总结一下以上的算法,可以看到,当计算右数第 i 位包含的 X 的个数时:
- 取第 i 位左边(高位)的数字,乘以 10i−1 ,得到基础值 a 。
- 取第
i
位数字,计算修正值:
- 如果大于 X,则结果为 a+10i−1 。
- 如果小于 X,则结果为 a 。
- 如果等 X,则取第 i 位右边(低位)数字,设为 b ,最后结果为 a+b+1 。
相应的代码非常简单,效率也非常高,时间复杂度只有 O(log10n) 。
public static int count1(int givenNumber, int searchKey){
//cnt计数器,初始k为大于0的数
int cnt = 0, k=1;
//i是factor,用来得到每个位置上的数字
for (int i = 1;k>0;i *= 10) {
//通过k知道每一位上的基本出险次数
k = givenNumber / i;
cnt += (k / 10) * i;
//当前位上的数字
int cur = k % 10;
if (cur > searchKey) {
cnt += i;
} else if (cur == searchKey) {
//第i位的低位加上1
cnt += givenNumber - k * i + 1;
}
}
return cnt;
}
当 X = 0 时,规律与上面给出的规律不同,需要另行考虑。
最主要的区别是,最高位中永远是不会包含 0 的,因此,从个位累加到左起第二位就要结束,需要将上面代码中 for 循环的判断条件改为 k / 10 != 0。
其次是,第 i 位的基础值不是高位数字乘以 10i−1 ,而是乘以 10i−1−1 。以 1 至 102 为例,千位中实际包含 3 个 0,但这三个 0 是来自于个位 2 计算得到的修正值,而非来自于基础值。千位的基础值是 0,因为不存在数字 01, 02, 03, ..., 09,即数字前是没有前导 0 的。解决办法就是将上面代码中第 6 行改为 cnt += (k / 10 - 1) * i。
public static int count(int n, int x) {int cnt = 0, k=1;
for (int i = 1;k>0;i *= 10) {
// 高位的数字。
k = n / i;
int high = k / 10;
if (x == 0) {
if (high!=0) {
high--;
} else {
break;
}
}
cnt += high * i;
// 当前位的数字。
int cur = k % 10;
if (cur > x) {
cnt += i;
} else if (cur == x) {
// n - k * i 为低位的数字。
cnt += n - k * i + 1;
}
}
return cnt;
}