【剑指offer】1~n整数中1出现的次数

博客聚焦于面试题“1~n整数中1出现的次数”。先指出直接思路时间复杂度高,不符合要求。接着介绍《剑指Offer》解法繁琐,后引入国外大佬的解法,通过设定整数点分析各数位上1的个数,分三种情况讨论,最后累计求和得出结果,时间复杂度为O(logn)。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

面试题43:1~n整数中1出现的次数

题目:输入一个整数 n,求 1~n这n个整数的十进制表示中1出现的次数。例如,输入12,1~12这些整数中包含1的数字有1、10、11和12,1一共出现了5次。 

比较直接一点的思路,在不考虑时间效率的情况下,可以循环1~n,对每个数都先判断个位数是不是1,如果这个数大于10则除10后再判断这个数个位数是不是1,这种算法思路比较简单直接,而且算法时间复杂度也不符合要求O(nlogn),就不展开详细解释。

function NumberOf1(n){
    let number = 0;
    while(n){
        if(n%10 == 1)
            number++;

        n = n/10;
    }
    return number;
}

function Count(n){
    let sum = 0;
    for(let i = 1; i <= n; i++)
        sum += NumberOf1(i);
    return sum;
}

《剑指Offer》给出了提高时间效率的解法,但是代码十分繁琐,时间复杂度为O(logn),与数字的位数有关,而且不易于理解。这时候看到了一个大佬的解法,代码来自国外leetcode上的一个解答,核心代码只有短短的三行就解决了这个问题,时间复杂度也是O(logn),不得不说大佬牛x。我花了一定的时间进行了查找资以及分析,把代码稍微扩展成我能理解的方式,在这里记录下我的思路。

国外大佬的解答:

class Solution {
public:
    int NumberOf1Between1AndN_Solution(int n)
    {

        int count=0;
        long long i=1;
        for(i=1;i<=n;i*=10)
        {
            //i表示当前分析的是哪一个数位
            int a = n/i,b = n%i;
            count=count+(a+8)/10*i+(a%10==1)*(b+1);
        }
        return count;
    }
};

主要思路:设定整数点(如1、10、100等等)作为位置点i(对应n的个位、十位、百位等等),分别对每个数位上有多少包含1的点进行分析。每个整数点都仅仅针对当前位(个位、十位、百位、千位等)计算包含1的个数,不考虑其他位上1的情况。

当 i = 1,就从个位开始,就计算从1~n,个位上出现了几次1。

当 i = 10,就从十位开始,就计算1~n,十位上出现了几次1。

当 i = 100,从百位开始,就计算1~n,百位上出现了几次1。

以此类推。

然后把他们全部加起来,就得到了1~n上所有位数出现的1的次数总和。


这里假定一种情况来讨论,其他相似类推就行了,本质上计算方法一样的。这里假设  i = 100 ,即当 i 表示百位的时候,求 1~n 百位上出现1的总次数。

根据设定的整数位置,对n进行分割,分为两部分,高位n/i,低位n%i。

分三种情况讨论即:

(1)百位大于等于2

(2)百位等于1

(3)百位等于0

(1) 当百位的情况大于等于2 (n = 31456, i = 100)

当 n = 31456,a = 314(a为高位,百位和百位之前的数),b = 56(b为低位,百位之后的数),当前百位的数字为 4 >= 2,百位出现1的次数为 a/10+1,分别为 001,011,021,031,041,051,061,071,081,091,101...301,311共32次(000~314)。只考虑百位为 1 的情况,百位的 1 出现了 32 次,算上低位每次1-100的完整循环,1-n上百位共出现了 (a/10+1)*100 次个1。(31+1)*100 = 3200次

(2) 当百位的情况等于 1 (n = 31156, i = 100)

当 n = 31156,百位的情况等于 1 的时候,有 a = 311, b = 56,此时百位对应的就是1,则百位出现的次数为a/10,分别为 001,011,021,...301 共 31 次(000-309),当最高两位为31(即a=311),本次没有完整的1-100的循环,因为低位最多只到56为止,所以共b+1次 = 56+1 = 57次(算上 b = 0 的时候),1-n 百位1共出现了(a/10*100)+(b+1)  = (31*100)+57 = 3157 次

(3)当百位的情况等于 0 的时候

当 n = 31056,a = 310,b = 56的时候,百位对应的数为0,此时百位为1的次数有a/10=31(最高两位0~30)。所以出现1的次数为 (a/10*100) = 31*100 = 3100 次

综合以上三种情况,依次从个位、十位、百位...开始遍历到最高位,把每一位出现1的次数进行累计求和即是最终的结果。

function getNumberOf1 (n) {
    let digit = 1 // 当前遍历的位,从各位开始
    let res = 0   // 统计1出现的总数

    let high = Math.floor(n/10), cur = n % 10, low = 0

    while(high != 0 || cur !=0 ) {
        if(cur == 0) res += high * digit
        else if(cur == 1) res += high * digit + low + 1
        else res += (high + 1) * digit

        low += cur * digit
        cur = high % 10
        high = Math.floor(high/10)
        digit *= 10
    }
    return res
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值