这是来自《编程之美》2.4中的题目,我只给出我的新方法,书上的方法这里略过不提,因为网上已经有很多解释书上的内容的博文了。
以下是分析:
我们先从最简单的数字开始分析。
0~9:个位数>=1时,1的出现次数均为1.个位数=0时,出现0次1.......................................................................①
10~99:十位数字=1时,比如17.先分析10~17中所有两位数中个位数上1的个数=1,再分析10~17所有两位数中十位数上1的个数=17-10+1,然后再分析1~9中所有个位数1的出现个数=1,所以总和为第一次循环计算10~17所有两位数中个位数中1的个数为1+第二次循环计算10~17所有1出现次数和2+17-10(1~10中1的个数+11~17所有十位数上1的出现次数)=10
十位数>1时,比如58,先分析10~58所有两位数中个位数上1的个数=5,再分析10~58所有两位数中十位数上1的个数=19-10+1,然后再分析1~9中所有个位数1的出现个数=1,所以总和为第一次循环计算个位数中1的个数为1+第二次循环计算10~58中所有1出现次数和10+(2-1)*5=16。
十位数=0时,转到①执行。
100~999:百位数=1时,比如123,先分析101~123中个位数+十位数中1的个数和相当于求0~23的所有1出现次数为13,再计算101~123中百位数上的1的个数和=123-101+1.然后计算1~100中1出现次数和。所以总和为(21+123-100)(百位上1的出现次数+100以前所有1出现次数)+13(1~23所有1的个数和相当于100~123中十位数和个位数1的个数和)。
百位数>1时,比如246,先分析前200中1的出现次数=前99中1的出现次数+100~200中1的出现次数=21+101~200中个位和十位1个数和+百位数1的个数和=21+前99中的出现次数和+101~200中百位数1的出现次数和=21-1+21-1+100=100+(21-1)*2.然后再计算200~246中十位和个位数1的个数和(相当于求0~46所有1的个数和15)。最后总和=100+(21-1)*2+15=155
百位数=0时,转到上面十位数的情况。
我写的程序是,比如计算123,先计算1~3中1的出现次数和,然后再次循环计算1~23中1的出现次数和,这些都计算好了,第3次循环才计算1~123中1的出现次数和。另外在求给定数字之前,我们先把n=10,100,1000,10000........诸如这些整数求好存到数组里,然后利用这些数来求给定数。
现在给出代码:
#include <iostream> #include <time.h> using namespace std; #define n 11//11位 __int64 f(int x) { __int64 s=1; while (x--) { s*=10; } return s; } __int64 Total_Count(__int64 x,__int64 a[]) { int temp[n]={0},i=0,t=x; __int64 sum=0,s=1,q=0; while (x) { temp[i++]=x%10; x/=10; } for (int j=0;j<i;j++) { q+=temp[j]*s; if (temp[j]>1) { if(s==1) sum+=1; else sum+=s+(a[j]-1)*temp[j]; } else if(temp[j]==1) { if(s==1) sum+=1; else sum+=a[j]+q-s; } s*=10; } return sum; } void main() { long i = 10000000L; clock_t start, finish; double duration; /* 测量一个事件持续的时间*/ printf( "Time to do %ld empty loops is ", i ); start = clock(); srand(unsigned int (time(NULL))); __int64 a[n+1]={1,2}; for (int k=2;k<=n;k++) { a[k]=10*a[k-1]+f(k-1)-9; } int t=0;//注释部分是求1出现次数和最大正整数相等时的值,通过这个循环比较两种方法的运行时间不失为一种好的方法。 /*for (__int64 j=1;j<1000000000;j++) { if (Total_Count(j,a)==j)//这里换成下面那个书上的函数作为对比,您就知道这种方法的效率了。 { printf("%I64d\n", j); t++; } }*/ cout<<endl; printf("%I64d\n", Total_Count(246,a)); cout<<"共"<<t<<"个"<<endl; finish = clock(); duration = (double)(finish - start) / CLOCKS_PER_SEC; printf( "%f seconds\n", duration ); }
下面给出《编程之美》上面的代码作为对比:
__int64 Count(__int64 m){ //1的个数 __int64 count = 0; //当前位 __int64 Factor = 1; //低位数字 __int64 LowerNum = 0; //当前位数字 __int64 CurrNum = 0; //高位数字 __int64 HigherNum = 0; if(m <= 0){ return 0; } while(m / Factor != 0){ //低位数字 LowerNum = m - (m / Factor) * Factor; //当前位数字 CurrNum = (m / Factor) % 10; //高位数字 HigherNum = m / (Factor * 10); //如果为0,出现1的次数由高位决定 if(CurrNum == 0){ //等于高位数字 * 当前位数 count += HigherNum * Factor; } //如果为1,出现1的次数由高位和低位决定 else if(CurrNum == 1){ //高位数字 * 当前位数 + 低位数字 + 1 count += HigherNum * Factor + LowerNum + 1; } //如果大于1,出现1的次数由高位决定 else{ //(高位数字+1)* 当前位数 count += (HigherNum + 1) * Factor; } //前移一位 Factor *= 10; } return count; }