"1"的个数,算法研究(一)

来源:互联网 发布:tcp网络编程 客户端 编辑:程序博客网 时间:2024/05/17 22:38

一.问题描述:
给定一个十进制正整数N,计算从1开始,到N中所有整数中出现"1"的个数
例如:
设total为包含的"1"的个数
N=3时,total=1;
N=13时,total=6;
N=123时,total=57;
...................
...................
解法一:一般方法
最简单的方法是直接从1开始遍历到N,算出其中每一个数种含有的1的个数,然后将它们全部加起来得到最终答案。
源程序如下:
#include<stdio.h>
int CountNumber(unsigned int n)
{
int num=0;
while(n>=10) //每次分离出个位数,将它与1比较
{
if(n%10==1)
{
num++;
}
n=n/10;
}
if(n==1) num++;
}

int main()
{
unsigned int n;
int i,total=0;
scanf("%d",&n);
for(i=1;i<=n;i++)
{
total=total+CountNumber(i);
}
printf("%d/n",total);
return 0;
}
分析:时间复杂度=O(N)x计算一个整数数字里面"1"的个数的复杂度=O(N*log2N),如果给定N特别大的话,效率就非常低了。
解法二
设n(个),n(十),n(百)分别表示个位数上,十位数上,百位数上可能出现的"1"的个数。ni(个),ni(十),ni(百)分别表示在个位数,十位数上百位数上的数字的大小。
通过猜想假设可以总结出如下规律:
对个位上的数:
当ni(个)=0时,n(个)=去掉个位数余下的数字组成的数的大小
如:当N=120时,n(个)=12;
当ni(个)=1时,n(个)=去掉个位数余下的数字组成的数的大小+1;
如:当N=121时,n(个)=12+1;
当ni(个)>1时,n(个)=去掉个位数余下的数字组成的数的大小+1;
如:当N=123时,n(个)=12+1;
对十位上的数:
当ni(十)=0时,n(十)=去掉十位数上的数字更高位形成的数的x10
如:当N=103时,n(十)=1x10=10;
当ni(十)=1时,n(十)=(去掉十位数上的数字更高位形成的数)x10+(地位所成数字+1)
如:当N=213时,n(十)=2x10+(3+1)=24;
当ni(十)>1时,n(十)=(去掉十位数后更高位形成的数+1)x10
如:当N=223时,n(十)=(2+1)x10=30;
对百位上的数:
当ni(百)=0时,n(百)=去掉百位数上的数字更高位形成的数的x100
如:当N=1023时,n(百)=1x100=100
当ni(百=1时,n(百)=(高位形成的数)x100+(低位所成数字+1)
如:当N=2123时,n(百)=2x100+(23+1)=224;
当ni(百)>1时,n(百)=(去掉百位数后更高位形成的数+1)x100
如:当N=1234时,n(百)=1+1)x100=200;

千位上或者更高位也有类似的规律。
分析后可以得到以下高效率的算法
源程序:
#include<stdio.h>
int main()
{
unsigned int n;
int total=0;
scanf("%d",&n);
int m=n;
int i=10;
while(m!=0) // 分别算出个位数,十位数,百位数.....的1的个数
{
switch(m%10)
{
case 0:
total=total+n/i;
break;
case 1:
total=total+n/i*(i/10)+n%(i/10)+1;
break;
default:
total=total+(n/i+1)*i/10;
}
i=10*i;
m=m/10;
}
printf("%d/n",total);
}
分析:输入长度问哦Len的数字N的时间复杂度为O(Len),即为O(ln(n)/ln(10)+1),
经测试,计算N=1000 000 000时,速度至少比第一种方法快40 000倍