剑指offer-整数中1出现的次数(从1到n整数中1出现的次数)
题目:整数中1出现的次数(从1到n整数中1出现的次数)
求出1~13的整数中1出现的次数,并算出100~1300的整数中1出现的次数?为此他特别数了一下1~13中包含1的数字有1、10、11、12、13因此共出现6次,但是对于后面问题他就没辙了。ACMer希望你们帮帮他,并把问题更加普遍化,可以很快的求出任意非负整数区间中1出现的次数(从1 到 n 中1出现的次数)。
思路分析
方法一:统计1-n之间1出现的次数,首先我们可以采用暴力解法直接数,从n到1,对于每个数都进行判断,如果含有1,则计数器加一。判断的方法是将每个数当成字符串,对于字符串的每一位是否为1进行判断即可.。
实现代码:
1 public class Solution { 2 public int NumberOf1Between1AndN_Solution(int n) { 3 int count=0; 4 while(n>0){ 5 String str=String.valueOf(n); 6 char [] chars=str.toCharArray(); 7 for(int i=0;i<chars.length;i++){ 8 if(chars[i]=='1') 9 count++; 10 } 11 n--; 12 } 13 return count; 14 } 15 }
现在来分析一下上面代码的时间复杂度,总的外循环是从n到1,循环了n次,而内部的循环与n的位数有关。所以总的循环判断次数=(9)*1+(99-10+1)*2+(999-100+1)*3+(9999-1000+1)*4+...
总的时间复杂度是比较接近O(n)线性的,但是要大于O(N)
方法二:仔细观察也是可以找到规律的。
以n=12345为例子,12345=10000+2000+300+40+5,12345可以从每一位上单独进行分析,我们以i表示位数标识,以百位为例
i=100 此时a=12345/100=123 , b=12345%100=45 可以拆成前后两部分的,对于两部分可以先分开计算然后最后再相乘或相加(计数原理)
- 如果百位上的数字大于等于2(此时为3),则前面部分共可以出现123/10+1次,即(00~12)共能出现13次1,而每个1又重复了100次(如00100~00199,01100~01199,12100~12199等, ) 故总的次数=(a/10+1)*100
- 如果百位上的数字等于1,如n=23145,此时a=231,b=45 此时对于前面部分共可以出现a/10+1(00~23)共24次1,但是此时只有前23个能重复100次,最后一个23100~23145只能有46次,这就与前一种情况不同,所以总的次数=a/10*100+(b+1)
- 如果百位上的数字等于0,如n=12045 ,此时a=120 ,b=45,此时对于前面的部分共可以出现a/10(00~11)共12次1,这12个1能重复100,后面的已经没有百位上的1出现了,所以总的次数=a/10*100
由于每一位上的情况都可以与百位上的情况相同,所以我们可以对以上规律进行总结归纳
如果百位上的数字大于等于2或是等于0,那么出现1的次数=(a+8)/10*100
(为什么加8,我们从上面可以看出大于等于2会比等于0在前面多出现一次1,为了得到对应的结果我们直接让大于等于2的向前进位后再取整,而等于0的由于其没有什么进位所以不会影响其结果)(如果题目由此变形一下,求2出现的次数,那么同理我们便可以划分为大于等于3的情况,等于2的情况,小于2的情况,此时可能就要加7)
如果百位上的数字等于1,那么总的次数由两部分组成,次数=a/10*100+(b+1)
让i从1开始逐步变为10,100,1000,。。。便可求出各个位上的1的数目,在循环的同时求和就行
代码如下:
1 public class Solution { 2 public int NumberOf1Between1AndN_Solution(int n) { 3 long count=0; 4 long i=1; 5 for(i=1;i<=n;i*=10){ 6 long a=n/i,b=n%i; 7 count=count+(a+8)/10*i+(a%10==1?1:0)*(b+1); 8 } 9 return (int)count; 10 } 11 }