[力扣]面试题43. 1~n整数中1出现的次数

统计1的个数

Posted by JinFei on April 6, 2020

题目描述

输入一个整数 n ,求1~n这n个整数的十进制表示中1出现的次数。

例如,输入12,1~12这些整数中包含1 的数字有1、10、11和12,1一共出现了5次。

Example1:

  输入:n = 12
    输出:5

Example2:

  输入:n = 13
    输出:6

限制

  • 1 <= n < 2^31

解题思路

  • 参考
      本题主要是找规律。暴力算法会超时。
      我们先来看一个简单的规律:
      设f(n)是只从0到n位数的最大值一共有多少个1,
      f(1) = 计算0~9有多少个1 = 1;
      f(2) = 计算0~99有多少个1;
    
      我们先把1开头的十位数单独拿出来考虑且只考虑十位数上1的个数:10~19共有10个1;
      然后再考虑个位数上1的个数,此时十位数范围从0~9,每种十位数对应1个个数均为f(1);
      综合得f(2) = 10 + 10 * f(1) = 20;
      同理 f(3) = 计算0~999有多少个1
    
      先把1开头的百位数单独拿出来考虑,且只考虑百位数:100~199,共100个1;
      再考虑其他位数上1的个数,此时百位数范围从0~9,每种百位数对应1个数均为f(2);
      综合得 f(3) = 100 + 10 * f(2) = 300;
      针对题目中数字最大为2^31次方,最多只有10位数。所以综上所述我们可以列出f(n)
      f(1) = 1;
      f(2) = 20;
      f(3) = 300;
      f(4) = 4000;
      f(5) = 50000;
      f(6) = 600000;
      f(7) = 7000000;
      f(8) = 80000000;
      f(9) = 900000000;
      f(10) = ‭10,000,000,000‬;
    
      针对随便一个数如2345,如何计算其包含的1的个数。
      同理规律中的计算:
    
      首先考虑千位数 即0345~2345共多少个1
      只考虑千位数:1000~1999:共1000个
      除了千位数后续其他位数:2 * f(3) = 600个
      考虑百位数及其后续位数 即0045~0345 共有多少个1
      只考虑百位数:100~199 :100个
      除了百位后续其他位:3 * f(2) = 60个
      考虑十位及其后续位数,即0005~0045共有多少个1
      只考虑十位:10~19:共 10个
      十位后的其他位:4 * f(1) = 4个
      最后考虑个位:0000~0005共有多少个1
      如果个位不为0,则有1个
      如果个位为0 则有0个
      共1600 + 160 + 14 + 1 = 1775
      好,有了上述步骤后,因为上述步骤没有出现某一位<=1 ,我们还需考虑如果某一位<=1是怎样的情况,
      如1045:
      在考虑千位数时,只考虑千位数的结果就不是1000了,而是1000~1045,共46个
      同时在考虑百位数值,只考虑百位数的结果就不是100了,而是000~000,共0个
    
      首先考虑千位数 即0045~1045共多少个1
      只考虑千位数:1000~1045:共46个
      除了千位数后续其他位数:1 * f(3) = 300个
      考虑百位数及其后续位数 即0045~0045 共有多少个1
      只考虑百位数:000~000 :0个
      除了百位后续其他位:0 * f(2) = 0个
      考虑十位及其后续位数,即0005~0045共有多少个1
      只考虑十位:10~19:共 10个
      十位后的其他位:4 * f(1) = 4个
      最后考虑个位:0000~0005共有多少个1
      *如果个位不为0,则有1个
      *如果个位为0 则有0个
      共:346 + 0 + 14 + 1 = 361个
    
class Solution {
public:
    int getRest(stack<int> s){
        s.pop();
        int res = 0;
        while(!s.empty()){
            res = res * 10 + s.top();
            s.pop();
        }
        return res + 1;
    }

    int countDigitOne(int n) {
        // 计算f(1~10)
        vector<long>help = vector<long>(11);
        help[1] = 1;
        for(int i = 2; i < help.size(); i++){
            help[i] = pow(10, i - 1) + 10 * help[i - 1];
        }
        
        //用栈来记录数字的各个位数上的值
        stack<int> s = stack<int>();
        while(n){
            s.push(n % 10);
            n /= 10;
        }
        //res 记录结果
        long res = 0;
        while(!s.empty()){
            int size = s.size();
            int top = s.top();
            //判断个位
            if(size == 1){
                // 如果个位为0则不++  / 如果个位不为0 则增加一个1个数
                if(top != 0)    res += help[1];
            }
            else{
                //判断除了个位的其他位
                if(top > 1)         res += pow(10, s.size() - 1);
                else if(top == 1)   res += getRest(s);
                res += top * help[size - 1];
            }
            s.pop();
        }
        return res;
    }

};