基本思路
计算在 0~n 中所有数的第 k 位上 x 出现的次数——问题的核心就是怎么实现这样一个函数。
记 n 中数位 k 左边的数大小是 l ,右边的数大小是 r ,数位 k 上的数大小是 mid ,则对所有第 k 位是 x 的数 m 可以做这样的分类:
1. 数位 k 左边的数在 [0, l-1] 中。
意味着 m 一定不会超出 [0, n] 的范围。
这时 k 位的右边几个数位可以随意取值,所以一共有 l*10^k 个这样的 m 在 [0, n] 中。
2. 数位 k 左边的数恰好是 l 。
这时不能确定 m 是不是会超出 [0, n] 的范围,所以要分类讨论。
1) mid 小于 x
一定超出了范围。
2) mid 等于 x
第 k 位右边的数可以在 [0, r] 中任意取值,所以 [0, n] 内有 r+1 个这样的 m 。
3) mid 大于 x
一定没有超出范围,所以 [0, n] 内有 10^k 个这样的 m 。
3. 数位 k 左边的数大于 l 。
一定超出范围。
边界情况
考虑一下边界情况需不需要特殊处理。
1. 第 k 位是 n 的最低位,即 k 等于 0 。
按上面的分类方法可以正常得出结果,不需要特殊处理。
2. 第 k 位是 n 的最高位。
最高位不可能取 0 ,所以需要对 x 等于 0 的情况做特判,提前返回。
其他情况可以正常得出结果。
3. 第 k 位超出了 n 的最高位。
需要特判,提前返回。
4. x 等于 0 。
这时第 k 位左边的数不能取 0 ,需要特判。
参考资料
算法基础课
代码
#include <iostream>
using namespace std;
int p[10];
void init () { // 预处理一个 10 的 i 次方数组
p[0] = 1;
for (int i = 1; i <= 8; i ++ ) {
p[i] = p[i - 1] * 10;
}
}
int get (int n, int x, int k) { // 计算 0~n 中第 k 位出现了多少次 x
int l = n / p[k + 1]; // 首先截取第 k 位左边一坨、右边一坨,以及第 k 位自身
int r = n % p[k];
int mid = n / p[k] % 10;
if (!l && !mid) { // 如果第 k 位已经超出了 n 的最高位返回 0
return 0;
}
if (!l && !x) { // 最高位不可能出现 0
return 0;
}
int ans = 0;
if (l) {
ans += (x ? l : l - 1) * p[k]; // x 等于 0 时第 k 位左边的数只能在 [1, l-1] 上取值
}
if (mid > x) {
ans += p[k];
} else if (mid == x) {
ans += r + 1;
}
return ans;
}
int get (int n, int x) { // 计算 0~n 中 x 出现了多少次
int ans = 0;
for (int i = 0; i < 8; i ++ ) { // 累加 0~7 八个数位的结果
ans += get (n, x, i);
}
return ans;
}
int main () {
init();
int a, b;
while (cin >> a >> b && a) {
if (a > b) { // 保证 a 是比较小的那一个
swap (a, b);
}
for (int i = 0; i < 10; i ++ ) {
cout << get (b, i) - get (a - 1, i) << ' ';
}
cout << '\n';
}
return 0;
}