1. C++二级考试奇葩楼层问题解析
最近在辅导学生准备电子学会C++二级考试时,遇到了一道很有意思的真题——"奇葩楼层"问题。这道题不仅考察了基础编程能力,还涉及实际生活中的场景模拟,特别适合作为二级考试的典型例题。下面我就带大家详细拆解这道题的解题思路和实现方法。
1.1 问题描述与理解
题目是这样的:某栋楼有个奇葩的规矩,所有包含数字k的楼层都跳过不计数(比如k=4时,4楼、14楼、24楼...都不存在)。现在给定总楼层数n和忌讳数字k,要求计算出实际存在的楼层数量。
举个例子:
- 输入:n=10, k=1
- 输出:9
解释:1-10层中跳过包含1的楼层(1,10),实际存在2,3,4,5,6,7,8,9共9层
这个问题的核心在于如何判断一个数字是否包含特定数字k。看似简单,但需要考虑多种边界情况,非常考验编程基本功。
1.2 解题思路分析
解决这个问题主要有以下几个关键步骤:
- 数字检查:对于1到n的每个数字,检查是否包含数字k
- 计数机制:不包含k的数字计数,包含的跳过
- 输出结果:最终统计有效的数字个数
其中最关键的是第一步——如何高效检查数字是否包含特定数字。这里我推荐两种方法:
方法一:字符串转换法
- 将数字转换为字符串
- 使用字符串的find方法查找k
- 如果找不到就计数
方法二:数学分解法
- 对数字逐位取模分解
- 检查每一位是否等于k
- 全部位检查完毕再决定是否计数
对于二级考试来说,字符串方法更直观易懂,代码也更容易实现。下面我们就用这种方法来编写解决方案。
2. 代码实现与详细解析
2.1 基础版本实现
我们先来看一个最基础的实现方案:
cpp复制#include <iostream>
#include <string>
using namespace std;
int countValidFloors(int n, int k) {
int count = 0;
for (int i = 1; i <= n; ++i) {
string s = to_string(i);
if (s.find(to_string(k)) == string::npos) {
count++;
}
}
return count;
}
int main() {
int n, k;
cin >> n >> k;
cout << countValidFloors(n, k) << endl;
return 0;
}
这段代码虽然简单,但有几个关键点需要注意:
- to_string的使用:将数字转换为字符串,方便查找
- string::npos:表示查找失败的返回值
- 循环范围:从1到n(包含n)的闭区间
2.2 优化版本实现
基础版本虽然能解决问题,但效率上还有提升空间。我们可以做以下优化:
cpp复制#include <iostream>
#include <string>
using namespace std;
int countValidFloors(int n, int k) {
int count = 0;
char target = '0' + k; // 提前计算目标字符
for (int i = 1; i <= n; ++i) {
string s = to_string(i);
if (s.find(target) == string::npos) {
count++;
}
}
return count;
}
int main() {
int n, k;
cin >> n >> k;
cout << countValidFloors(n, k) << endl;
return 0;
}
优化点包括:
- 提前计算目标字符:避免在循环中重复转换k
- 使用char比较:比字符串比较更高效
2.3 数学方法实现
为了对比,我们再看看数学分解法的实现:
cpp复制#include <iostream>
using namespace std;
bool containsDigit(int num, int k) {
if (num == 0 && k == 0) return true;
while (num > 0) {
int digit = num % 10;
if (digit == k) return true;
num /= 10;
}
return false;
}
int countValidFloors(int n, int k) {
int count = 0;
for (int i = 1; i <= n; ++i) {
if (!containsDigit(i, k)) {
count++;
}
}
return count;
}
int main() {
int n, k;
cin >> n >> k;
cout << countValidFloors(n, k) << endl;
return 0;
}
这种方法虽然代码稍长,但不依赖字符串操作,在某些情况下可能更高效。
3. 关键考点与技巧
3.1 考察的核心知识点
这道题主要考察以下几个C++二级考点:
- 循环结构:for循环的熟练使用
- 字符串处理:to_string和find方法的应用
- 条件判断:if语句的逻辑控制
- 函数封装:将功能模块化为函数
- 输入输出:基本的cin/cout操作
3.2 常见错误与调试技巧
在教学中,我发现学生容易犯以下错误:
-
边界条件处理不当:
- 忘记包含n本身
- 处理k=0时的特殊情况
- 数字0是否应该被检查
-
类型转换问题:
- 直接将数字与字符比较
- 忘记将k转换为字符串或字符
-
效率问题:
- 在循环内重复创建字符串
- 不必要的类型转换
调试时可以:
- 先测试小范围数据(如n=10)
- 打印中间结果检查
- 特别注意k=0和k=1的情况
3.3 性能优化建议
对于大型n(比如n>10^6),我们可以考虑以下优化:
- 数学规律法:计算不含k的数字数量,类似数位DP的思想
- 预处理:提前构建有效数字表
- 并行计算:将任务分块并行处理
不过对于二级考试来说,基础的字符串方法已经完全够用了。
4. 扩展思考与实际应用
4.1 问题变种与扩展
这道题可以有多种变体,适合作为练习:
- 多忌讳数字:跳过包含任意一个忌讳数字的楼层
- 连续数字:跳过包含连续两个k的楼层(如k=1时跳过11,112等)
- 数字和限制:跳过数字和为特定值的楼层
4.2 实际应用场景
这类问题在实际中有很多应用:
- 电梯系统设计:处理特殊楼层编号
- 数据过滤:筛选不含特定数字的ID
- 游戏开发:设计关卡编号规则
- 车牌管理:避免不吉利的数字组合
4.3 教学建议
在教学中,我建议:
- 先从简单案例入手(如n=20)
- 可视化数字检查过程
- 鼓励学生想出不同的解决方案
- 讨论各种方法的优缺点
这道题很好地展示了如何将实际问题抽象为编程问题,是培养计算思维的优秀例题。通过这道题的练习,学生可以巩固基础知识,同时提升问题分析和解决能力。