跳转至

L 到 R 中有多少个 2?

先弄清楚:数“出现次数”,还是数“整数个数”?

本题统计闭区间 [L, R](包括 L 和 R)中,所有整数的十进制写法里,数字 2 一共出现多少次。不添加前导零。

例如从 20 到 23:

整数 数字 2 出现次数
20 1
21 1
22 2
23 1

答案是 5,不是 4。如果题目问“有多少个整数含有 2”,答案才是 4。

先会:for 循环、数位拆分。

拆成两件小事

  1. 外层:从 L 到 R,一个数一个数地选。
  2. 内层:拆开当前数的每一位,碰到 2 就让总次数加一。

拆数时必须使用副本 x。不能在内层一直修改外层变量 i,否则会破坏遍历顺序。

完整程序

本入门例子约定 0 <= L <= R <= 1000000。范围很大时,不要直接照搬暴力枚举。

#include <iostream>
using namespace std;

int main() {
    int L, R;
    cin >> L >> R;
    long long answer = 0; // 整个区间的总次数
    for (int i = L; i <= R; i++) {
        int x = i; // 每次换一个新的副本
        do {
            int digit = x % 10;
            if (digit == 2) {
                answer++;
            }
            x /= 10;
        } while (x > 0);
    }
    cout << answer << '\n';
    return 0;
}

输入 20 23,输出 5。

为什么变量放在这里?

  • answer 在外层循环之前初始化:它要保留整个区间的总数。
  • x 在外层循环里面初始化:每换一个 i,都从这个新数开始拆。
  • 遇到一个 2 后不能 break:22 中还有第二个 2。

若只统计“有多少个数含有 2”,才可以发现一个 2 就计数并停止拆当前数。

需要做多少次?

假设区间有 m = R - L + 1 个数,每个数最多 d 位,大约检查 m × d 个数位,时间复杂度写作 O(m d),额外空间为 O(1)。

本例每个数最多 7 位,适合入门练习。如果区间扩大到 0 到 10^18,逐个数枚举就不现实,需要学习按数位统计等进阶方法。

用这些数据检查自己

输入 输出 检查什么
0 0 0 0 里没有 2
2 2 1 L 和 R 相等也要检查
22 22 2 一个数可以贡献多次
1 30 13 十位和个位都要统计
99 101 0 区间跨位数也没关系

试着改一改

把题目改为“统计数字 3 出现的次数”,要改哪里?如果统计数字 0 呢?

提示与答案

将 digit == 2 改为 digit == 3 或 digit == 0。

本程序用 do while,会把整数 0 的表示当作一位 0。因此统计数字 0 时,区间 [0, 0] 的答案是 1,[10, 10] 的答案也是 1。

下一步:枚举入门,学会判断哪些题可以“一个一个试”。