1、问题描述
输入一个整数n,求从1到n的这n个整数的十进制表示中,1出现的次数。例如输入12,则1到12这些整数中包含1的有1,10,11,12,1共出现了5次。
2、解题思路
-
边界条件:(1)不合法输入,输入整数为0或负;
-
思路1:对于1到n的每个整数,分别计算其十进制表示中1出现的次数,最后将这n个数的计算结果累加起来,就得到了1总共出现的次数。这个方法的关键是如何统计一个整数的十进制中1出现的次数:可以利用求一个数的十进制表示中各数位上的数字算法来解决这个问题,即统计该整数各个数位中1的个数。
-
思路2:这道可以从数字出发找规律。其实在《编程之美》中就总结了相应的规律:
-
(1)1的数量:
-
如果第iii(从右向左编号,编号从1开始)位的数字为0,那么第iii位可能出现1的次数times(i)times(i)times(i)由更高位决定(若无最高位,视最高位为0),且times(i)times(i)times(i)=高位所有数字 * 当前位的权重10i−110^{i-1}10i−1。例如:输入数字为4039,那么百位数字出现1的数字有100-199,1100-1199,2100-2199,3100-3199。百位出现1的次数times(3)=4∗102=400times(3)=4*10^{2}=400times(3)=4∗102=400;
-
如果第iii位的数字为1,那么第iii位数可能出现1的次数times(i)times(i)times(i)不仅由更高位决定,也受更低位的影响(若无最高位,视最高位为0),即times(i)times(i)times(i)=高位数字 * 10i−110^{i-1}10i−1 + 低位数 + 1;
-
如果第iii位的数字大于1(2-9),那么第iii位数可能出现1的次数times(i)times(i)times(i)由更高位决定,同时还要加上第i位为1的情况,即times(i)times(i)times(i)=高位数字 * 10i−1+10i−110^{i-1} + 10^{i-1}10i−1+10i−1 ;
-
(2)X的数目:
-
从1到10的这10个数,在它们的个位中,任意的X都出现了1次;
-
从1到100的这100个数,在它们的十位数中,任意的X都出现了10次;
-
从1到1000的这1000个数,在它们的百位数中,任意的X都出现了100次;
-
从1到10i10^{i}10i的这10i10^{i}10i个数,在它们的第iii位数中,任意的X都出现了10i−110^{i-1}10i−1次。
-
下面以n=435,X=3为例,计算1-n这n个数X出现的次数:
-
首先是个位(第1位),其高位=43,低位=0,由于个位数字5>X,所以个位数出现X的次数由高位决定,最后再加上个位等于X的情况,即出现次数=43∗100+100=43+1=4443*10^{0}+10^{0}=43 + 1=4443∗100+100=43+1=44;
-
其次是十位(第2位),其高位=4,低位=5,由于十位数字3=X,所以十位数出现X的的次数不仅由最高位决定,也由最低位决定,即出现次数=4∗101+5+1=464*10^{1}+ 5+1=464∗101+5+1=46;
-
最后是百位数(第三位),其高位=0,低位=25,由于百位数4 > X,出现X的的次数由最高位决定,然后加上百位等于X的情况,即出现次数=0∗102+102=1000*10^{2} + 10^{2}=1000∗102+102=100
-
总结一下以上的算法,可以看到,当计算右数第 iii 位包含的 XXX 的个数时:
-
取第iii位左边(高位)的数字,乘以 10i−110^{i-1}10i−1 ,得到基础值 aaa 。
-
取第 iii 位数字,计算修正值:
-
如果大于 XXX,则结果为 a+10i−1a+ 10^{i−1}a+10i−1 。
-
如果小于 XXX,则结果为 aaa 。
-
如果等于 XXX,则取第 iii 位右边(低位)数字,设为 bbb ,最后结果为 a+b+1a+b+1a+b+1 。
-
相应的代码非常简单,效率也非常高,时间复杂度只有 O(log10n)O( log 10 n)O(log10n)
3、代码实现
public class Problem_32 {
public int NumberOf1Between1AndN_Solution(int n) {
if (n <= 0){
return 0;
}
int count = 0;
String n_str = String.valueOf(n);
for(int i=1;i<n_str.length() + 1;i++){
int high = n/(int)Math.pow(10,i);
int temp = n % (int)Math.pow(10,i);
int low = temp % (int)Math.pow(10,i-1);
int current = temp / (int)Math.pow(10,i-1);
int base = high * (int)Math.pow(10,i-1);
if (current < 1){
count += base;
}
else if (current == 1){
count += base + low + 1;
}
else {
count += base +(int) Math.pow(10,i-1);
}
}
return count;
}
}