Loading

剑指 Offer 43. 1~n 整数中 1 出现的次数

思路

这题如果直接用暴力法,1~n逐一判断,每个数逐位判断需要O(L)的时间,其中L为n的位数,所以总的时间复杂度为O(L*n),这显然会超时。

方法:逐位判断,找规律

假设n是4位数abcd, 即n=abcd,从右往左逐位分析:
- 对于n中的第4位数d:
    - 如果d ≥ 1,对于1~n中第4位数为1的数,则其他3位数可以为000~abc,共1+abc种情况,即(000~abc)1;
    - 如果d = 0, 对于1~n中第4位数为1的数,则其他3位数可以为000~abc-1,共1+abc-1种情况,即(000~abc-1)1。

- 对于n中的第3位数c:
    - 如果c>1,对于1~n中第3位数为1的数,其他3位数可以是000~ab9,共有1+ab9种情况,即该数可以为(00~ab)1(0~9);
    - 如果c=1,对于1~n中第3位数为1的数,其他3位数可以是000~abd,共有1+abd种情况,即该数可以为(00~ab)1(0~d);
    - 如果c=0,对于1~n中第3位数为1的数,其他3位数可以是000~(ab-1)9,共有1+(ab-1)9种情况,即该数可以为(00~ab-1)1(0~9)。
    
- 对于n中的第2位数b:
    - 如果b>1,对于1~n中第2位数为1的数,其他3位数可以是000~a99,共有1+a99种情况,即该数可以为(0~a)1(00~99);
    - 如果b=1,对于1~n中第2位数为1的数,其他3位数可以是000~acd,共有1+acd种情况,即该数可以为(0~a)1(00~cd);
    - 如果b=0,对于1~n中第2位数为1的数,其他3位数可以是000~(a-1)99,共有1+(a-1)99种情况,即该数可以为(0~a-1)1(00~99)。
    
- 对于n中的第1位数a:
    - 如果a>1,对于1~n中第1位数为1的数,其他3位数可以是000~999,共有1+999种情况,即该数可以为1(000~999);
    - 如果a=1,对于1~n中第1位数为1的数,其他3位数可以是000~bcd,共有1+bcd种情况,即该数可以为1(000~bcd);

以上只是拿4位数举例子,无论n是几位数,规律都是和上面一样,可以依此法编写代码。
 

举例:

    拿21058举个例子,从右往左逐位分析:
    对于第5位8:
        对于1~n中第5位数为1的数,左边的四位可以是0000~2105,共有2106种情况,即该数可以为(0~2105)1
    对于第4位5:
        对于1~n中第4位数为1的数,剩余的四位可以是0000~2109,共有2110种情况,即该数可以为(0~210)1(0~9)
    对于第3位0:
        对于1~n中第3位数为1的数,剩余的四位可以是0000~2099,共有2100种情况,即该数可以为(00~20)1(00~99)
    对于第2位1:
        对于1~n中第2位数为1的数,剩余的四位可以是0000~2058,共有2059种情况,即该数可以为(0~2)1(000~058)
    对于第1位2:
        对于1~n中第1位数为1的数,剩余的四位可以是0000~9999,共有10000种情况,即该数可以为1(0000~9999)
    因此,共有2106+2110+2100+2059+10000 = 18375种情况。
 
注意:这种方法并没有漏算,比如110中有两个1,当判断到第2位的时候,第1位的1被算进去了,判断到第1位的时候,第2位的1也被算进去了,所以110中计算了两种情况,这和题目是相符的。

复杂度分析

时间复杂度:O(L2),其中L为数字n的长度(位数),在此处 L= log10n

空间复杂度:O(1)

 
根据以上分析,可以写出以下代码: 
 1 class Solution {
 2 public:
 3     int countDigitOne(int n) {
 4         int res = 0;
 5         int x = n;
 6         
 7         //首先计算n的总位数len
 8         int len = 0;
 9         while(x) {
10             len++;
11             x /= 10;
12         }
13 
14         x = n;  // 假设n是4位数abcd, x=n=abcd
15         
16         for(int i = len; i >= 1; --i) {
17             int m = x % 10; //m为当前第i位数的值
18             x /= 10;        //x为当前第i位的左侧数字的大小(也就是n的前i位数的大小)
19             if(i == len) {
20                 if(m >= 1) {
21                     res += 1 + x;
22                 } else {
23                     res += 1 + (x-1);
24                 }
25             } else {
26                 if(m > 1) {
27                     int rightDigitNum = len-i;
28                     int t = x;
29                     for(int j = 1; j <= rightDigitNum; ++j) {
30                         t = t*10 + 9;
31                     }
32 
33                     res += 1 + t;
34                 } else if(m == 1) {
35                     int t = x;
36                     int r = 1;
37                     int rightDigitNum = len-i;
38                     for(int j = 1; j <= rightDigitNum; ++j) {
39                         r *= 10;
40                         t *= 10;
41                     }
42 
43                     //n % r表示当前位右侧所有数字的大小
44                     //最终算出来的t表示数字n去掉第i位数之后,剩余的数字的大小
45                     t +=  n % r;    
46                     res += 1 + t;
47 
48                 } else {    // m == 0
49                     int rightDigitNum = len-i;
50                     int t = x-1;
51                     for(int j = 1; j <= rightDigitNum; ++j) {
52                         t = t*10 + 9;
53                     }
54 
55                     res += 1 + t;
56 
57                 }
58             }
59         }
60         return res;
61     }
62 
63 };

 

 

 

posted @ 2020-11-07 14:01  拾月凄辰  阅读(91)  评论(0编辑  收藏  举报