【23】剑指offer——整数中1出现的次数

本文深入解析了求解1~n中数字1出现次数的问题,提供了三种解题思路,包括暴力解法、按位统计优化解法及高效算法。通过观察和归纳,总结出适用于任意非负整数区间的通用解法。

题目描述

求出1~13的整数中1出现的次数,并算出100~1300的整数中1出现的次数?为此他特别数了一下1~13中包含1的数字有1、10、11、12、13因此共出现6次,但是对于后面问题他就没辙了。ACMer希望你们帮帮他,并把问题更加普遍化,可以很快的求出任意非负整数区间中1出现的次数(从1 到 n 中1出现的次数)。

解题思路:

思路1:暴力,原本以为过不了,竟然过了 。。。 

代码如下:(都不好意思粘贴代码了 7^7

class Solution {
public:
    int NumberOf1Between1AndN_Solution(int n)
    {
        if (n == 0){
            return 0;
        }
        int ans = 1;
        int tol;
        for (int i = 2; i <= n; i++){
            tol = i;
            while (tol != 0){
                if (tol%10 == 1){
                    ans++;
                }
                tol = tol / 10;
            }
        }
        return ans;

    }
};

思路2:咳咳,接下来我们用温柔的解法来解释这道题目,其实这道题目就是总结找规律的题目,我之前想到的是分别统计每个位数(个位、十位、百位 。。)上“1”的个数,然后求和即可,奈何当时思路不清晰,没有总结出一个统一的规律,之后稍加梳理,便可以总结出相应的规律。

首先我们来看个位,

对于个位来说,“1”的出现仅仅是1,11上的个位,21上的个位等等,可以看出1的出现周期是每10个出现一次,因此我们可以通过个位以上的高位数字得到个位上1的个数,同时要注意特殊情况!当数字是20的时候,只出现了2个1,但是十位有进位,因此当个位是0的时候我们需要特殊处理。综上,我们可以得到个位上出现1的个数为:

n/10 * 1+(n%10!=0 ? 1 : 0)

我们接下来看十位数出现1的个数,十位出现1仅仅是在10~19,110~119。。。这里把10~19定义为一个完整的周期,因此我们可以得到,当十位数字大于1时,就会有完整的周期出现,这时十位数字出现1的个数仅仅是由十位的高位数字所就决定;当十位数字等于1时,这时存在一个不完整的周期,这个不完整周期中1的个数由十位的低位数字决定;当十位数字等于0时,缺少一个完整周期。综上,可以得到十位数字出现1的个数为,

  • 设k = n % 100,即为不完整周期的数字
  • 归纳式为:(n / 100) * 10 + (if(k > 19) 10 else if(k < 10) 0 else k - 10 + 1)

下面我们继续来看百位,有了十位做铺垫,我们很容易就可以推导出百位上1出现的个数,即当百位数字大于1,有完整的整周期,由高位数字决定整周期的数目;当百位数字等于1,存在不完整周期,由低位数字决定不完整周期中1的个数;当百位数字等于0,缺少一个完整周期,综上,可以得到百位数字上1出现的个数,

  • 设k = n % 1000
  • 归纳式为:(n / 1000) * 100 + (if(k >199) 100 else if(k < 100) 0 else k - 100 + 1)

这时我们已经发现了规律,把个位的计算公式也写成通式的形式,即,

  • k = n % 10
  • 个位数上1的个数为:n / 10 * 1 + (if(k > 1) 1 else if(k < 1) 0 else k - 1 + 1)

综上,我们可以得到这个题的答案是:

  • k = n % (i * 10)
  • count(i) = (n / (i * 10)) * i + (if(k > i * 2 - 1) i else if(k < i) 0 else k - i + 1)
  • sum1 = sum(count(i)),i = Math.pow(10, j), 0<=j<=log10(n)

看牛客网的大神觉得太多的if-else让代码变得复杂,所以想把判断语句改为更简单的形式,即当不完整周期出现的时候选择不完整周期,不完整周期不存在的时候(为负数或i)选择0 或 i,具体表达式如下:

min(max((n mod (i*10))−i+1,0),i)

按照这个思路写的代码如下:

class Solution {
public:
    int NumberOf1Between1AndN_Solution(int n)
    {
        if (n == 0){
            return 0;
        }
        int ans = 0;
        for (int i = 1; i <= n; i = i * 10){
            long dri = i * 10;
            int a = n%dri - i + 1;
            if (a > 0){
                if (a > i){
                    ans += i;
                }
                else{
                    ans +=a;
                }
            }
            else {
                if (i > 0){
                    ans += 0;
                }
                else {
                    ans += i;
                }
            }
            ans += n/dri*i;
        }
        return ans;
    }
};

思路3:研究思路2的推导,我们发现不论是否存在不完整周期,1的个数都由当前位数的高位决定整体数目,低位决定不完整周期的1的个数,因此对n进行分割,分为两部分,高位n/i,低位n%i

当i表示百位,且百位对应的数>=2,如n=31456,i=100,则a=314,b=56,此时百位为1的次数有a/10+1=32(最高两位0~31),每一次都包含100个连续的点,即共有(a%10+1)*100个点的百位为1

当i表示百位,且百位对应的数为1,如n=31156,i=100,则a=311,b=56,此时百位对应的就是1,则共有a%10(最高两位0-30)次是包含100个连续点,当最高两位为31(即a=311),本次只对应局部点00~56,共b+1次,所有点加起来共有(a%10*100)+(b+1),这些点百位对应为1

当i表示百位,且百位对应的数为0,如n=31056,i=100,则a=310,b=56,此时百位为1的次数有a/10=31(最高两位0~30)

综合以上三种情况,当百位对应0或>=2时,有(a+8)/10次包含所有100个点,还有当百位为1(a%10==1),需要增加局部点b+1

这里补8非常巧妙,是因为当百位为0,则a/10==(a+8)/10,当百位>=2,补8会产生进位位,效果等同于(a/10+1)。

代码如下:

链接:https://www.nowcoder.com/questionTerminal/bd7f978302044eee894445e244c7eee6?f=discussion
来源:牛客网

    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;
    }
};

其实通过思路2和思路3的分析,我们不仅可以得到1的个数,也可以得到数字1~9任意一个数字出现的次数,同时数字0情况比较特殊,需要单独进行分析。

参考资料:https://www.nowcoder.com/questionTerminal/bd7f978302044eee894445e244c7eee6?f=discussion 牛客网

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值