LeetCode Number of Digit One

Given an integer n, count the total number of digit 1 appearing in all non-negative integers less than or equal to n.
For example:
Given n = 13,
Return 6, because digit 1 occurred in the following numbers: 1, 10, 11, 12, 13.

以前应该做过类似的题,最早好像是在一次笔试里做到的。先求出[0,9],[0,99],[0,999]这些规整范围内的1的个数。然后对于一个数比如2132就可以把它拆分成

  • [0,1000)
  • [1000,2000)
  • [2000, 2132]

这么几个部分,前面两个非常规整可以直接用已经计算好的[0, 999]中的1的个数直接算出。即看做两个分别由0打头和1打头的[0,999]范围,当然因为在1打头的情况下,其范围内每个数都贡献了一个开头的1要进行特殊考虑。第三个区间(如果打头的不是1)则等效与求[0,132]范围内的1的个数,此时我们已经将问题缩小了,不断进行这个分解过程直到个位为止。下面是代码

class Solution {
private:
    int mag[20];
public:
    int countDigitOne(int n) {
        for (int i=0; i<20; i++) {
            mag[i] = -1;
        }
        mag[1] = 1;
        
        int pw = 1;
        int t = n;
        int mg = 1;
        while (t > 9) {
            t/=10;
            pw*=10;
            mg++;
        }
        return dfs(n, pw, mg);
    }
    
    int dfs(int n, int pw, int mg) {
        if (pw < 2) {
            return n >= 1 ? 1 : 0;
        }
        int d = n / pw;
        int r = n % pw;
        int cnt = 0;
        for (int i=0; i<d; i++) {
            cnt += mcount(mg - 1);
        }
        if (d > 1) {
            cnt += pw;
        } else if (d == 1) {
            cnt += r + 1;
        }
        
        return cnt + dfs(r, pw / 10, mg - 1);
    }
    
    int mcount(int m) {
        if (m < 0) {
            return 0;
        }
        if (mag[m] > 0) {
            return mag[m];
        }

        int pw = 1;
        for (int i=1; i<m; i++) {
            pw *= 10;
        }
        return mag[m] = (mcount(m-1) * 10 + pw);
    }
};

代码里递归都是尾递归,可以写成迭代形式,懒得改了。

果然自己的方法无比挫逼,看到这个之后:

class Solution {
public:
    int countDigitOne(int n) {
        long pw = 1;
        int cnt = 0;
        while (n / pw > 0) {
            int r = n % pw;
            int d = (n / pw) % 10;
            int h = (n / pw) / 10;
            
            switch (d) {
                case 0:
                    cnt += h * pw;
                    break;
                case 1:
                    cnt += h * pw + r + 1;
                    break;
                default:
                    cnt += (h + 1) * pw;
            }
            pw *= 10;
            
        }
        return cnt;
    }
};

其实这个switch里判断可以合并成一两条语句,不过还是这样放着最好理解,没必要装逼。注意pw可能会溢出,如果一定全用int类型的话,可以和INT_MAX/10做比较。
这样整个过程按数字位遍历即可得出结果,如一个数2134可以这样考虑:

  • 213_
    即在范围[0, 2130)这个区间内,在个位上会出现多少个1,这个问题应该超级好回答,就是213个,卧槽,随机选了个数字答案好讽刺。当然如果当前数字是>=1的话还要213+1=214,这个例子中最后一位是4所以在整个[0,2134]区间中个位上出现1的个数是214个。
  • 21_4
    再来看看十位上1的个数的计算方法,有个计算个位的经验似乎可以立马得出一项即十位上1的个数为21个,不过这个有问题。因为十位与个位不同它后面可以带数字也就是说在一个数字前缀比如20前提下它可以有2010,2011,2012...2019这么10种,所以应该为21x10 = 210个,同样我们需要考虑当前数字是不是大于等于1的情况,如果是<1那么后面的数字无论是几都没有影响(因为十位上的不是1后缀怎么变都不能出现十位为1的情况)。当=1时还需要加上后面数字+1个1(因为数是从0开始数的比如13,它所代表的情况有10,11,12,13),当>1时,实际上已经包含了整个十位取值为1的范围,比如这个例子中的2134,十位为3那么它肯定包括了21[10,19]的情况
原文地址:https://www.cnblogs.com/lailailai/p/4639307.html