剑指offer43---1-n整数中1出现的次数

1-n整数中1出现的次数

这个题目暴力解决貌似可以通过,但时间复杂度太大了。

法一 剑指offer版

比如数字21345,可以把他分为2段,1-1345    1346-21345

先看1346-21345,因为最高位是2,大于1,因此,只看最高位的话,1出现的次数就是10000-19999,即10000次

看低位,我们发现包括1346-9999 0000-9999 0000-1345这3部分,其实也就是求2个 0000-9999中1出现的次数,我们可以总结规律:

在4位数中,任选一位是1,其余三位可以在0-9这10个数字中任意选择,因此根据排列组合的原理,出现的次数为4*10^3=4000,再乘以2即800。至于为什么可以用排列组合,可以这么考虑:任选的一位比如1000,即选择第一位即千位是1,那么千位是1的数字个数为10^3,那么如果任选的是百位,那么百位是1的个数也是10^3,依次共有4中选择,所以10^3.

至于1-1345中出现的次数,可以用递归来解决。

因此,这可以看作一个递归问题

    char strn[50];
    int numberof1(const char *s)
    {
        if(!s||*s=='\0')
            return 0;
        int first=*s-48;
        int length=strlen(s);
        int res_count=0;
        if(first==0&&length==1)
            return 0;
        if(length==1&&first>0)
            return 1;
        
        int numberfirstdigit=0;
        if(first>1)
            numberfirstdigit=int(pow(10,length-1));
        else if(first==1)
            numberfirstdigit=atoi(s+1)+1;
        
        int numberotherdigit=first*(length-1)*int(pow(10,length-2));
        
        int numrecursive=numberof1(s+1);
        return numberfirstdigit+numberotherdigit+numrecursive;
    }
    
    int NumberOf1Between1AndN_Solution(int n)
    {
        if(n<=0)
            return 0;
        int i=0;
        while(n)
        {
            strn[i++]=n%10+48;
            n=n/10;
        }
        strn[i]='\0';
        int k=0,m=i-1;
        while(k

法二 

这一版思路比较清晰。

比如数字13524。

我们可以考虑百位,百位是5,大于等于2,那么百位是1的数就有100-199,1100-1199,2100-2199,3100-3199 ......10100-10199,11100-11199,12100-12199,13100-13199,则百位共有(13+1)*100个1,13是百位前的数字

再看数字13110

考虑百位,百位是1,小于2,那么百位是1的数就有100-199,1100-1199,2100-2199,......10100-10199,11100-11199,12100-12199,13100-13110,则百位共有13*100+10+1个,13是百位前的数字,10是百位后的数字

再看数字13011

百位是0,那么百位是1的数字有100-199,1100-1199,2100-2199......10100-10199,11100-11199,12100-12199,则百位共有13*100个1,13是百位前的数字。

因此,某位上出现1的次数可能会受到该位高位数字和低位数字的影响。

    int NumberOf1Between1AndN_Solution(int n)
    {
        int numof1=0;
        int current=0,before=0,after=0;
        int i=1;    //从个位算起
        while(n/i)  //如果该位上有数字,即该位存在
        {
            current=n/i%10;
            before=n/(i*10);
            after=n%i;
            if(current>=2)
            {
                numof1+=(before+1)*i;
            }
            else if(current==1)
                numof1+=before*i+after+1;
            else
                numof1+=before*i;
            i=i*10;
        }
        return numof1;
    }

注意怎么计算高位低位(不一定是单位数)和当前位(是一个0-9的数字)

法三

最简单的方法

对于数字3141592

先看个位数,为2,大于1,那么置个位数为1,前边的数字可以从0-314159之间随意排列,所以共有314160种可能(即个位数为1的情况数)

再看十位数,为9,大于1,那么置十位数为1,前面的数字可以从0-31415之间随意排列,十位数后面的数字也可以选择0-9,因此共有314160种可能。

再看百位,为5,大于1,置百位为1,前面的数字从0-3141之间随意排列,百位后面的数字也可以从0-99,因此共有314200种可能

再看千位,为1,比较特殊,虽然前面的数字可以从0-314之间随意排列,但当是314时,后面的范围只可以时0-592,当为0-313时,后面的范围可以是0-999因此需要分开,即314*1000+593=314593

后面依次。。。

由于当某一位数字大于等于2时,该位为1时出现的情况不需要特殊处理,(该位前的数字+1)*位数值即可

当某一位数字小于2时,该位为1时出现的情况需要进行处理,即(该位前的数字)*位数值+(该位后的数字+1)*t(当该位数字为1时t=1,为0时t=0)

那如何判断大于还是小于2,可以采用加8看是否进位即可,如果进位,说明大于等于2,按照进位后的数据乘以位数值即可,如果没有进位,处理完前段再处理后段即可(处理后段时需要判断该位是为0还是为1)

    int NumberOf1Between1AndN_Solution(int n)
    {
        int numof1=0;
        if(n<=0)
            return 0;
        for(long long m=1;m<=n;m*=10)
        {
            int a=n/m,b=n%m;
            numof1+=(a+8)/10*m+(b+1)*(a%10==1);
        }
        return numof1;
    }

 

 

 

你可能感兴趣的:(剑指offer)