2021-07-29


传送门: 数字1的个数

题意

给定数字n,求所有不大于数字n的正整数中,出现的1的个数

举例9527

从最数位开始依次往低位遍历,则遍历顺序为:9 -> 5 -> 2 -> 7

数位9

首先不考虑本数位上的1,而考虑比本数位低位的数位上的1。
在该数位上,若我们这个数位上的数字为0-8的数字,则其他数位的数字可以任意选择,使得整体的数字不会超过9527.
若确定本数位为0-8,则此时剩下3个数位未确定,在这3个数位上,我们可以这样安排数字,使得其出现1:(1* *) 或 (* 1 *) 或 (* * 1)
其中 * 代表可以用任何数字替换,不难得出3个数位中,任意组合的所有数字中1的个数一共是 3*100= 300 个,而0-8一共有9个数字,所以此时答案需要加上9*300 = 2700个1。
其次考虑本数位上的1。
因为0-8中可以选择1,考虑选择1的情况,则可知一共有1000个数字(1000-1999)包含1,所以此时答案需加上1000个1。
至此 0000-8999 我们已经考虑完毕

数位5

接下来考虑 9000-9499
在该数位上,若我们这个数位上的数字为0-4的数字,则其他数位的数字可以任意选择,使得整体的数字不会超过9527.
若确定本数位为0-4,则此时剩下2个数位未确定,在这2个数位上,我们可以这样安排数字,使得其出现1:(1 *) 或 (* 1)。
不难得出2个数位任意组合的所有数字中的1的个数一共是2*10 = 20个,而0-4一共有5个数字,所以此时答案需要加上5*20 = 100个1。
因为0-4中可以选择1,考虑选择1的情况,则可知一共有100个数字(9100-9199)包含1,所以此时答案需加上100个1。
至此 9000-9499 我们已经考虑完毕

数位2

接下来考虑 9500-9519
在该数位上,若我们这个数位上的数字为0-1的数字,则其他数位的数字可以任意选择,使得整体的数字不会超过9527.
若确定本数位为0-2,则此时剩下1个数位未确定,在这1个数位上,我们可以这样安排数字,使得其出现1:(1 )。
不难得出2个数位任意组合的所有数字中的1的个数一共是1*1 = 1个,而0-1一共有2个数字,所以此时答案需要加上2*1 = 2个1。
因为0-1中可以选择1,考虑选择1的情况,则可知一共有10个数字(9510-9519)包含1,所以此时答案需加上10个1。

至此 9500-9519 我们已经考虑完毕

数位7

接下来考虑 9520-9527
在该数位上,若我们这个数位上的数字为0-7的数字,因为是最后一位了,仅能选择1。答案加上1
至此 9520-9527 我们已经考虑完毕

因此答案是 2700 + 1000 + 100 + 100 + 2 + 10 + 1 = 3913

注意

举例 1798
则数位遍历: 1 -> 7 -> 9 -> 8
当遍历至 1 时,由于选择1时不能任意改变低位的数字,比如 1999 就不行,所以考虑本数位的1而进行的计算,应当加上剩下的低位数字表示的最大数字+1,即798+1 = 799。为什么要+1呢?因为1000需要被考虑在内。

举例 1078
数位遍历至0时,不用进行任何操作。

答案

class Solution {
    typedef long long LL;
public:
  
    int countDigitOne(int n) {
        //n为1时,big为1
        //n为985时,big为100
        //n为100时,big为100
        //n为3567时,big为1000,
        //以此类推
        //big是用来从高位遍历至低位的
        LL big = 1;
        //len是big的总位数,ans是答案
        int len = 1,ans = 0;
        //分别代表了不同个数位个数能组合出的所有数字包含的1的个数
        int cal[11]={0,1,20,300,4000,50000,600000,7000000,80000000,900000000,1000000000};
        //计算big和len
        while(big< n){
            big*=10;
            ++len;
        }
        //比方说n = 100,上面的代码计算后big = 100,此时不用除以10
        if(big!=n){
            big/=10;
            --len;
        }
        //遍历未完毕
        while(n){
            //now是当前位的最大数字
            int now = n/big;
            //如果now不为0
            if(now){
                //不考虑本数位上的1 :本数位可为0,1,2 ... now-1
                ans+=now*cal[len-1];
                //考虑本数位上的1 :
                //若本数位大于1,则本位取1,低位可以任意取
                //若本数位等于1,则只能取低位拼凑成的最大数字
                ans+=now>1?big:n%big+1;
            }
            //去掉最高位
            n%=big;
            //相应地进行缩小
            --len;
            big/=10; 
        }
        return ans;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值