1~n整数中1出现的次数--最高效率的解法

0x01.问题

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

C++函数形式:    int countDigitOne(int n) 

0x02.详细分析

初看这个问题感觉比较简单,但细细想想,越想发现越难,因为这个数可能很大,暴力枚举肯定不能解决问题了。所以我们一定要找其他办法解决。

我刚开始傻傻的数出了1-99,1-999,的1的个数,因为我总感觉里面存在某些制约关系,或者存在一些规律,后来一想,我的确傻啊,我为啥不写一段代码专门测试一下,看是否存在规律,于是我写了下面这段测试代码:

#include<iostream>
using namespace std;

int main() {  
    while (1) {
        int n,m, ans = 0;
        cout << "请输入区间左端点:";
        cin >> n;
        cout << "请输入区间右端点:";
        cin >> m;
        for (int i = n; i <= m; i++) {
            int temp = i;
            while (temp) {
                int w = temp % 10;
                if (w == 1) ans++;
                temp /= 10;
            }
        }
        cout <<"该区间 1 的个数是     "<< ans << endl;
     }
}

发现这段代码还是真香,于是,我开始了我的测试:

在这里插入图片描述
怎么样,发现规律了嘛,原来在一个指定位数的区间是有规律的,这样,我们发现,心中瞬间有底了。

我们就可以开始假设一些情况了,假如数字是19989,是个5位数,这样,我们1-9999就不用计算了,因为我们已经知道了,接下来,只要考虑后面的数字的情况就行了。

后面的数字我们就产生疑惑了,如果第一个数字是2,或者其它不是1的数呢?很明显产生的1的个数不同,这怎么办呢?

于是我又开始去找找规律:

在这里插入图片描述

咦,原来1开头的空间也都是有规律的,于是我接着找不是1开头的数:

在这里插入图片描述
神奇的事又来了,原来除了1开头的,其它的整区间都是相等的。

我们现在把发现的规律整理一下:

  • 位数之间的大区间(也就是10,100,100这样的区间)含1的个数都是
    位数*10^(位数-1)
  • 1开头的数字,在当前位数(不包含低位)含1的个数规律是:100-1991201000-1999130010000-1999914000,依此类推。
  • 不是1开头的数字,在当前位数含1的个数规律是:三位的都是20,四位的都是300,五位的都是4000,依次类推。

理清上面的规律后,我们可以开始想办法去解决问题了。

我们再来看19989,我们要计算这个的含1个数,是不是只要四位数的含1数加上,本位含1数,而开头是1,所以肯定是多了99891的,然后就只要计算9989这个四位数了,依次类推,最后就只要计算到各位就行了,这其实就是递归的思路了,但我发现,这个写出递归代码似乎有点复杂,于是我就仿造了一个递归代码,就是写多个分位数计算的函数,之间相互调用,哈哈,这也是一种解决办法。而且我们会发现,最大数字是2^31,也就是2,147,483,648,十位数,所以我们可以把这些情况都枚举出来。

我们的整体思路是:

  • 先判断n的位数,然后选择调用不同的函数。

  • 对于一个指定位数的计算,需要分多步计算:

    • 先加上低位数的全部1的个数。
    • 如果最高位等于1,那么ans先加1,目的是包含最高位的最小数字(如:19987,这个步骤的含义是加上10000的1的个数),然后ans加上低位数字,这是最高位产生的1,再把低一位的传给上一个计算这个低位的计算。
      -如果最高位不等于1ans先加上相应的1作为最高位产生的个数,再加上之前几个最高位产生的个数(如,计算5676,这个步骤就是加上2000-4999的个数),最后把低一位的传给相应的函数计算。
  • 最后返回得到答案ans

举例:98789

  • 先加上四位数的个数4000,然后最高位不是1,加上10000-19999产生的14000,然后加上20000-89999产生的,也就是(8-2)*4000,最后将8789传给相应的函数计算。

0x03.解决代码–最高效率

class Solution {
public:
    int f1=1;
    int f2=20;
    int f3=300;
    int f4=4e3;
    int f5=5e4;
    int f6=6e5;
    int f7=7e6;
    int f8=8e7;
    int f9=9e8;
    int basicCounter1(int n){
        return n!=0?1:0;
    }
    int basicCounter2(int n){
        if(n<10) return basicCounter1(n);
        int ans=0;
        ans+=f1;//1-9
        int ten=n/10;
        if(ten==1){
            ans++;//10
            ans+=n-10+basicCounter1(n-10);
        }
        else{
            ans+=11;//10-19
            ans+=ten-2;
            ans+=basicCounter1(n-ten*10);
        }
        return ans;
    }
    int basicCounter3(int n){
        if(n<100) return basicCounter2(n);
        int ans=0;
        ans+=f2;//1-99
        int hundred=n/100;
        if(hundred==1){
            ans++;//100
            ans+=(n-100)+basicCounter2(n-100);//二位数
        }
        else{
            ans+=120;//100-199
            ans+=(hundred-2)*f2;//前面几个百位的值
            ans+=basicCounter2(n-hundred*100);//二位数
        }
        return ans;
    }
    int basicCounter4(int n){
        if(n<1000) return basicCounter3(n);
        int ans=0;
        ans+=f3;//1-999
        int q=n/1000;
        if(q==1){
            ans++;//1000
            ans+=(n-1000)+basicCounter3(n-1000);//三位数
        }
        else{
            ans+=1300;//1000-1999
            ans+=(q-2)*f3;//前面几个前位值
            ans+=basicCounter3(n-q*1000);//三位数
        }
        return ans;
    }
    int basicCounter5(int n){
        if(n<1e4) return basicCounter4(n);
        int ans=0;
        ans+=f4;
        int w=n/1e4;
        if(w==1){
            ans++;
            ans+=(n-1e4)+basicCounter4(n-1e4);
        }
        else{
            ans+=14e3;
            ans+=(w-2)*f4;
            ans+=basicCounter4(n-w*1e4);
        }
        return ans;
    }
    int basicCounter6(int n){
        if(n<1e5) return basicCounter5(n);
        int ans=0;
        ans+=f5;
        int sw=n/1e5;
        if(sw==1){
            ans++;
            ans+=(n-1e5)+basicCounter5(n-1e5);
        }
        else{
            ans+=15e4;
            ans+=(sw-2)*f5;
            ans+=basicCounter5(n-sw*1e5);
        }
        return ans;
    }
    int basicCounter7(int n){
        if(n<1e6) return basicCounter6(n);
        int ans=0;
        ans+=f6;
        int bw=n/1e6;
        if(bw==1){
            ans++;
            ans+=(n-1e6)+basicCounter6(n-1e6);
        }
        else{
            ans+=16e5;
            ans+=(bw-2)*f6;
            ans+=basicCounter6(n-bw*1e6);
        }    
        return ans;
    }
    int basicCounter8(int n){
        if(n<1e7) return basicCounter7(n);
        int ans=0;
        ans+=f7;
        int qw=n/1e7;
        if(qw==1){
            ans++;
            ans+=(n-1e7)+basicCounter7(n-1e7);
        }
        else{
            ans+=17e6;
            ans+=(qw-2)*f7;
            ans+=basicCounter7(n-qw*1e7);
        }
        return ans;
    }
    int basicCounter9(int n){
        if(n<1e8) return basicCounter8(n);
        int ans=0;
        ans+=f8;
        int y=n/1e8;
        if(y==1){
            ans++;
            ans+=(n-1e8)+basicCounter8(n-1e8);
        }
        else{
            ans+=18e7;
            ans+=(y-2)*f8;
            ans+=basicCounter8(n-y*1e8);
        }
        return ans;
    }
    int basicCounter10(int n){
        int ans=0;
        ans+=f9;
        int sy=n/1e9;
        if(sy==1){
            ans++;
            ans+=(n-1e9)+basicCounter9(n-1e9);
        }
        else{
            ans+=19e8;
            ans+=(sy-2)*f9;
            ans+=basicCounter9(n-sy*1e9);
        }
        return ans;
    }
    int countDigitOne(int n) {
        int ans=0,len=0;
        string s=to_string(n);
        len=s.size();
        switch(len){
            case 1:
                ans=basicCounter1(n);
                break;
            case 2:
                ans=basicCounter2(n);
                break;
            case 3:
                ans=basicCounter3(n);
                break;
            case 4:
                ans=basicCounter4(n);
                break;
            case 5:
                ans=basicCounter5(n);
                break;
            case 6:
                ans=basicCounter6(n);
                break;
            case 7:
                ans=basicCounter7(n);
                break;
            case 8:
                ans=basicCounter8(n);
                break;
            case 9:
                ans=basicCounter9(n);
                break;
            case 10:
                ans=basicCounter10(n);
                break;
        }
        return ans;
    }
};

0x04.简要说明

在这里插入图片描述
这个算法本来的暴力解法是O(N)的时间复杂度,但是我们使用这种思想后,时间和空间都大幅减少了。

我们来看一下神奇之处,别看这好像是个递归,但其实最多执行11次,这是常数级别的!!!

而且在每个函数执行的过程中,都只是简单的加法运算,所以时间复杂度也是常数级别的!!!

时间和空间都达到了最高的常数级别!!!

一般会有时间换空间,或者空间换时间这一说法,但,这是代码换空间和时间,就是代码多写了一点,将可能的十位数都列举出来了,但效率惊人!!!

ATFWUS --Writing By 2020–03–26~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ATFWUS

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值