题目来源 剑指offer
01 题目
输入一个整数n,求从1到n,这n个十进制整数1出现的次数。例如,12, 那么1到12中包含1的数字有{1,10,11,12},其中11包含两个1,故总共5个数。
02 解法1
看到这道题,我们的第一反应很容易想到,从1遍历到n,然后计算每个数中1的个数。
![64f53cf23c8b9db41b28d0288265ee72.png](https://i-blog.csdnimg.cn/blog_migrate/371cd1db8339d8f97bbcf58df31c76f8.jpeg)
我们分析一下时间复杂度,从1到n遍历这个值的时间消耗是 o(n)。
计算每个数中的1的个数花费的时间是log(n)。log(n)的不是一个精确的概念,只是说随着n的值的增大,它的整体时间和空间复杂度成对数式增长,是一个相对的概念。
03 解法2
解法1很容易理解不再赘述,我们重点讲解解法2.
以 214为例
- 个位数出现1的次数计算。
个位数的范围是0-9,它的值是一直在0-9之间循环。0-9出现一次。说明个位数出现一次1。那么214出现了多少次完整的0-9呢。
214/10=21次。个位本身是4,4>1。
21 + 1 = 22次。
- 十位出现1的次数计算,(只看十位数,个位数已经计算,百位数接下来下次计算)
十位数的范围同样是0-9,它出现几次0-9呢,看它的前一位 214。说明2次进位 两次循环0-9。
10(0-9),11(0-9),12(0-9),13(0-9),14(0-9),15(0-9),16(0-9),17(0-9),18(0-9),19(0-9)
[注* 10(0-9)表示 100,101,102,103,104,105,106,107,108,109,其他雷同]
和0(0-9),1(0-9),2(0-9),3(0-9),4(0-9),5(0-9),6(0-9),7(0-9),8(0-9)
那么此时十位出现1的次数是2*10,(为什么是10,因为每次10进制的一个数,个位数可以出现十次,如10,11,12,13,14,15,16,17,18,19)
我们再来看,百位为2时,十位本身是1,但是它不能按10次来计算,因为214个位为4,只有210,211,212,213,214.这几个数。所以是5位。为(214%10 + 1)
总的十位数出现的1的次数为 (214/100)*10 + (214 % 10 + 1)
另:如果十位数大于1,那么次数就是(214/100)*10 + 10。思考下为什么呢哈哈
- 百位数出现的次数计算
百位数出现一次1,则会有100个1。从100,到199,可以自行推算一下。因为百位已经是最后一位,所以只会有一次1出现。
故百位出现1的次数为 100
04 总结
数值为n
1.每个位出现1的次数,是以所处的位基准数计算的。如个位数出现一次1,就是1。十位数出现一次1,就是10次1。百位数出现一次1,就是100次1。
我么用一个base表示所处的位数。
2.看位数中出现几次(0-9),要看它前面的数值。如2233。
个位数出现223次(0-9) 223((n/base) * 1)次1 base = 1
十位数出现22次(0-9)220次((n/base) * 10) 1 base = 10
百位数出现2次(0-9)200次((n/base) * 100)1 base = 100
然后再看位数本身的值。个位数3,说明多余了一次1 (base=1)
十位数是3大于1,说明多余出现了10(base=10)次1 .
百位数是2大于1,说明多余出现了100(base=100)次1.
千位数是2大于1,说明多余出现了1000(base=1000)次1. 然后所有相加得结果。
public static int countDigit1(int n) { int base = 1;// 表示所在的位数 int count = 0; int round = n; // 计算每次除以base后的值 while (round > 0) { int reminder = round % 10; round /= 10; count += round * base; if (reminder > 1) { count += base; } else if (reminder == 1) { count += (n % base) + 1; } base *= 10; } return count;}