题目:输入一个整数 n,求 1~n 这 n 个整数的十进制表示中 1 出现的次数。例如,输入 12,1~12 这些整数中包含 1 的数字有 1、10、11 和 12,1 一共出现了 5 此。
对于 X ∈ [1, 9]
,1~n 整数中 X 出现的次数在算法一样。(0 的比较特殊,不过大致思想类似)。
解题思路:
分别以 12493、12593、12693 为例,计算数字 5 出现的次数。
其中,结果为 5 分别在个位、十位,直到万位出现的次数的和。
以 5 在百位上出现的次数为例进行讲解:
-
对于 12493,百位上位为 4 < 5,所以其更高位,即万、千位只能从 00 - 11 进行变换,即
01、02...10、11
进行变化,共 12 种;而其更低位,即十、个位只能从 00 - 99 进行变换(因为万千位最大也才 11,即 115XX,此时十个位最大可为 99,即 11599,小于 12493),共 100 种。因此,对于 12493,5 在百位出现的次数位 12 * 100 = 1200
-
对于 12593,百位上为 5 == 5。所以其更高位,即万、千位只能从 00 - 12 进行变换;而其更低位,就需要看情况了:
(1)当万千位为 00 - 11 的时候,百十位可以是 00 - 99 进行变换;
(2)当万千位为 12 的时候,因为不能大过 12593,因此只能从 00 - 93 进行变换,共 94 种。因此,对于 12493,5 在百位出现的次数位 12 * 100 + 94 = 1294
-
对于 12693,百位上为 6 > 5。所以其更高位,即万、千位能从 00 - 12 进行变换,共 13 种;而其更低位,即十、个位能从 00 - 99 进行变换(因为万千位最大也才 12,即 125XX,此时十个位最大可为 99,即 12599,小于 12693),共 100 种。
因此,对于 12693,5 在百位出现的次数位 13 * 100 = 1300
对于 5 在其他位上出现的次数,也类似。
算法的具体实现:
// 用于控制数字 X 出现的次数
private static int X = 1;
public int NumberOf1Between1AndN_Solution(int n) {
if (n < 1) return 0;
if (n < 10) return 1;
int sum = 0;
// 用于辅助判断是不是把 n 的每一位遍历完了
int tag = n;
// 用于记录位的索引,从 n 的个位向高位开始
int index = 1;
while (tag > 0) {
int mi = (int) Math.pow(10, index);
int num = n % mi;
// 得到当前位的更高位
int high = n / mi;
int mi2 = (mi / 10);
// 得到当前位
int cur = num / mi2;
// 得到当前位的更地位
int low = num % mi2;
// 记录当前位出现 X 的次数
int count = 0;
if (cur < X) {
count = high * mi2;
} else if (cur > X) {
count = (high + 1) * mi2;
} else {
count = high * mi2 + (low + 1);
}
tag /= 10;
++index;
System.out.println(count);
sum += count;
}
return sum;
}
一个数 n 有 ((O(log10 n) 取整) + 1) 位,因此上述算法的时间复杂度为 O(log10 n )。
为什么数 n 有 ((O(log10 n) 取整) + 1) 位?
因为假设 n ∈ [10x, 10x+1-1],且 n 为整数,则 n 对于的位数属于 x + 1。例如 199,其属于 [100, 999],有 log10 100 = 2,则 199 对应的位数为 2 + 1 = 3.