[编程之美] PSet2.4 统计1的数目



问题描述:

给定一个十进制正整数N,写下从1开始,到N的所有整数,然后数一下其中出现的所有”1“的个数。

例如:

N=12,我们会写下1,2,3,4,5,6,7,8,9,10,11,12,于是出现1的个数为5.

问题是:

           1.写一个函数f(N),返回1到N之间出现“1”的个数,比如f(12) = 5;

           2.满足条件“f(N) = N”的最大N是多少?


问题一:

             解法一:考虑从1遍历到N,寻找出所有出现1的个数之和。

                                      这个算法致命伤的效率较低,时间复杂度为O(N)*计算一个整数内1的个数复杂度,对于后半部分,由于每次计算一次都缩小10倍,因此复杂度为lgN(以10为底),故算法总复杂度O(N * lgN)。

//下面的代码是计算1-N中连续的一串数字中1出现的次数
//解法一:遍历1-N,寻找1出现的次数和
int findOneNum(int N)
{
    int count = 0;
    int temp = 0;
    for(int i=1 ; i<=N ; i++){
        temp = i;
        while(temp){
            if(temp%10 == 1){
                count++;
                temp = temp/10;
            }
        }
    }
    return count;
}

                   解法二:解法一硬伤是遍历,那么能否找到另外一种机制避免遍历1-N呢?可以通过归纳总结的方法找出规律进行快速计算。

下面是讨论的情况:

/*
伪代码形式:
以N位数为基础讨论1出现的次数
情况一:假设N只有1位
*/
if(N >= 1) count = 1;
else if(N = 0) count = 0;
//情况二:假设N有2位
int gewei = N%10;
int shiwei = N/10;
if(shiwei = 0)
    count = 1;//退化到情况一
else if(shiwei=1)
    count = (gewei+1) + (shiwei+1);//考虑13,十位出现1为10 11 12 13,个位出现1为1 11
else if(shiwei >= 2)
    count = 10 + (shiwei+1);//考虑33,十位出现1为10-19,个位出现1为1 11 21 31
/*
情况三:假设N有3位
直接考虑一个例子:113与123
对于113,百位出现1为100-113,即(113-100+1),对于十位出现1次数为10-13(二位数部分),以及110-113(三位数部分)
对于个位出现1为1 11 21 31 ··· 101 111总共11+1种(由十位百位决定)
*/
/*
情况四:假设N为abcde
由情况三分析可以知道某一位出现1的次数与三部分有关,以百位出现1次数进行分析:
由百位、低位、高位共同决定,如12013,百位为0,高位为12,低位为13*/
int baiwei ;
int baiwei_count;//百位给count的贡献
if(baiwei = 0)// 如12013,百位出现1:100-199 1100-1199 2100-2199 ···11100-11199(高位贡献)
    baiwei_count = gaowei*100;
else if(baiwei = 1)//如12113,百位出现1:100-199 1100-1199···11100-11199(高位贡献)和12100-12113(低位贡献)
    baiwei_count = gaowei*100 + (diwei+1);
else if(baiwei >= 2)//比如12213,百位出现1:100-199 1100-1199···12100-12199(高位贡献)
    baiwei_count = (gaowei+1)*100;

下面给出实现的伪代码:

//下面是实现统计1-N中1的数目的代码
//使用归纳分析方法分析数字规律
int sum1s(int N)
{
    int count = 0;
    int iFactor = 1;//变权因子,用于遍历N中不同位,作为本位权值
    int currentBit = 0;//本位
    int upperBit = 0;//高位
    int lowerBit = 0;//低位
    while(N/iFactor){
         currentBit = (N/iFactor) % 10;//分析12306,iFactor=10,此时要拿出0,123已经6赋值
         upperBit = N/(iFactor*10);
         lowerBit = N - (N/iFactor)*iFactor;
         switch(currentBit){
            case 0:
                   count += upperBit*iFactor;break;
            case 1:
                   count += upperBit*iFactor+lowerBit+1;break;
            default:
                count += upperBit*iFactor;break;
        }
        iFactor *= 10;
    }
    return count;
}


       这个方法只需要对N进行处理,避开了对1-N的遍历,时间复杂度由N的长度决定,即O(N的长度),由于为十进制数据,N的长度=lgN+1;故该算法复杂度为O(lgN + 1),注意到lg以10为底。

问题二:满足条件“f(N) = N”的最大N是多少?

               这个问题依旧可以使用归纳总结的方法找规律:

//确定最大的N,下面给出分析过程
/*
  num         f(num)

9以下         1
99以下      1*10+10*1=20  
999以下    1*100+10*10+100*1=300  
9999以下   1*1000+100*10+10*100+1*1000=4000
······
999 999 999以下   900 000 000
9 999 999 999以下  10 000 000 000
*/
/*设n为num的位数
由数字关系可以分析出f(10^n-1) = n*10^(n-1);
从f(n)递增关系可以看到当n大于一定值时,f(n)>n,如上表中f(10^10-1) > 10^10-1;
所以可以从n=10^10-1开始向前追溯(向0递减),依次检查是否有f(n)=n,得出的第一个满足条件的
1 111 111 110是满足f(n)=n的最大整数
*/
int sum1s(int N);
typedef long long LL
int main()
{
    const LL N=99999999999;
    for(LL i=N ; i>0 ; i--){
        if(sum1s(i)==i){
            printf("%11d\n" , i);
            break;
        }
    }
    return 0;
}





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值