首页 > 代码库 > 统计从1到n所有正整数中1出现的次数

统计从1到n所有正整数中1出现的次数

这是来自《编程之美》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;  
}  






统计从1到n所有正整数中1出现的次数