有这样一个函数f(n),对于任意正整数n,它表示从 0 到
n 之间出现“1”的个数,比如 f(1) = 1, f(13) = 6,请列出从 1 到 1234567890 中所有的 f(n) =
n 的n, 要求准确快速.
相信很多人都能立刻得出以下的解法:
for(n:N)
{
判断n包含1的个数;
累加计数器;
}
这是最直接的解法,但遗憾的是,时间复杂程度为O(N*logN)。因为还需要循环判断当前的n的各位数,该判断的时间复杂程度为O(logN)。
接下来就应该思考效率更高的解法了。说实话,这道题让我想起另外一道简单的算法题:
N为正整数,计算从1到N的整数和。
很多人都采用了循环求解。然后利用初等数学知识就知道S=N*(N+1)/2,所以用O(1)的时间就可以处理。
再回到本道题目,同理应该去寻找到结果R与N之间的映射关系。
分析如下:
假设N表示为a[n]a[n-1]...a[1],其中a[i](1<=i<=n)表示N的各位数上的数字。
c[i]表示从整数1到整数a[i]...a[1]中包含数字1的个数。
x[i]表示从整数1到10^i -
1中包含数字1的个数,例如,x[1]表示从1到9的个数,结果为1;x[2]表示从1到99的个数,结果为20;
当a[1]=0时,c[1] =
0;
当a[1]=1时,c[1] =
1;
当a[1]>1时,c[1]
= 1;
当a[2]=1时,c[2] =
a[1] +1+ c[1] + x[1];
当a[2]>1时,c[2]
= a[2]*x[1]+c[1]+10;
当a[3]=1时,c[3] =
a[2]*a[1] +1+ c[2] + x[2];
当a[3]>1时,c[3]
= a[3]*x[2]+c[2]+10^2;
......
以此类推
当a[i]=1时,c[i] =
a[i-1]*...*a[1] +1+ c[i-1]+x[i-1];
当a[i]>1时,c[i]
= a[i]x[i-1]+c[i-1]+10^(i-1);
编程之美之1的数目
1 的数目
给定一个十进制正整数 N,写下从
1 开始,到 N 的所有整数,
然后数一下其中出现的所有“1”的个数。
例如:
N= 2,写下
1,2。这样只出现了 1 个“1”。
N= 12,我们会写下 1, 2,
3, 4, 5, 6, 7, 8, 9, 10, 11, 12。这样,1的个数是 5。
问题是:
写一个函数f(N)
返回1到N之间出现的“1”的个数,比如f(12)=5。
这个问题看上去并不是一个困难的问题,因为不需要太多的思考,大家都能找到一个最简单的方法来计算
f(N),那就
是从1开始遍历到N,将其中每一个数中含有“1”的个数加起来,
自然就得到了从1到N所有“1”的个数的和.C语言实现如下:
#include
stdio.h
int Count1(int n)
{
int iNum=0;
while(n!=0)
{
iNum+=(n==1)?1:0;
n/=10;
}
return iNum;
}
int Count2(int n)
{
int iCount=0;
for(int i=0;i<=n;i++)
{
iCount+=Count1(i);
} return iCount; }
int main()
{
int x;
scanf("%d",&x);
printf("%d",Count2(x));
return 0;
}
但是这个算法的致命问题是效率,它的时间复杂度是O(N)×计算一个整数数字里面“1”的个数的复杂度
= O(N *log2 N),如果给定的 N
比较大,则需要很长的运算时间才能得到计算结果。我试着输入100000000,一共用了大概12秒,貌似有点长,不够比作者说的40S快多了,作者的电脑有点旧…….-
.-
解法二
先从一些简单的情况开始观察:
如果N是一位数,可以确定f(N)=1
如过是二位数,如果
N=13,那么从 1 到 13 的所有数字:1、2、3、4、5、6、
7、8、9、10、11、12、13,个位和十位的数字上都可能有
1,我们可以将它们分开来考虑,个位出现 1 的次数有两次:1 和 11,十位出现 1 的次数有 4 次:10、11、12 和
13,所以 f(N)=2+4=6。要注意的是 11 这个数字在十位和个位都出现了 1, 但是 11 恰好在个位为 1 和十位为 1
中被计算了两次,所以不用特殊处理,是对的。再考虑 N=23 的情况,它和 N=13 有点不同,十位出现 1 的次数为 10 次,从
10 到 19,个位出现 1 的次数为 1、11 和 21,所以f(N)=3+10=13。通过对两位数进行分析,我们发现,个位数出现
1 的次数不仅和个位数字有关,还和十位数有关:如果 N 的个位数大于等于 1,则个位出现 1 的次数为十位数的数字加 1;如果N
的个位数为 0,则个位出现 1 的次数等于十位数的数字。而十位数上出现 1 的次数不仅和十位数有关,还和个位数有关:如果十位数字等于
1,则十位数上出现 1 的次数为个位数的数字加 1;如
果十位数大于 1,则十位数上出现
1 的次数为 10。
f(13) = 个位出现1的个数
+ 十位出现1的个数 = 2 + 4 = 6;
f(23) = 个位出现1的个数
+ 十位出现1的个数 = 3 + 10 = 13;
f(33) = 个位出现1的个数
+ 十位出现1的个数 = 4 + 10 = 14;
…
f(93) = 个位出现1的个数
+ 十位出现1的个数 = 10 + 10 =
20;
接着分析 3
位数,
如果 N =
123:
个位出现 1 的个数为 13:1,
11, 21, …, 91, 101, 111, 121
十位出现 1 的个数为
20:10~19, 110~119
百位出现 1 的个数为
24:100~123
f(23)= 个位出现
1 的个数 + 十位出现 1 的个数 + 百位出现 1 的次数 = 13 + 20 + 24 = 57;同理我们可以再分析 4 位数、
位数。 根据上面的一些尝试,下面我们推导出一般情况下,从
N 得
到
f(N)的计算方法: 假设 N=abcde,这里 a、b、c、d、e 分别是十进制数 N
的各个数位上的数字。如果要计算百位上出现 1
的次数,它将会受到三个因素的影响:百位上的数字,百位以下(低位)的数字,百
位(更高位)以上的数字。如果百位上的数字为
0,则可以知道,百位上可能出现 1 的次
数由更高位决定,比如 12
013,则可以知道百位出现 1 的情况可能
是 100~199,1 100~1
199,2 100~2 199,…,11 100~11 199,
一共有 1 200
个。也就是由更高位数字(12)决定,并且等于更高
位数字(12)×当前位数(100)。
如果百位上的数字为
1,则可以知道,百位上可能出现 1 的次数不仅受更高位影响,还受低位影响,也就是由更高位和低位共同决定。例如对于 12
113,受更高位影响,百位出现 1 的情况是 100~199,1 100~1 199,2 100~2 199,…,11 100~11
199,一共 1 200个,和上面第一种情况一样,等于更高位数字(12)×当前位数(100)。但是它还受低位影响,百位出现 1
的情况是 12 100~12 113,一共114
个,等于低位数字(123)+1。 如果百位上数字大于 1(即为 2~9),则百位上可能出现
1的次数也仅由更高位决定,比如 12 213,则百位出现 1 的可能性为:100~199,1 100~1 199,2 100~2
199,…,11 100~11 199,12 100~12 199,一共有 1 300
个,并且等于更高位数字+1(12+1)
×当前位数(100)。通过上面的归纳和总结,我们可以写出如下的更高效算法来
计算
f(N):
#include
int Sumls(int n)
{
int iCount=0,iFactor=1,iLowerNum=0,iCurrNum=0,iHigherNum=0;
while(n/iFactor!=0)
{
iLowerNum=n-(n/iFactor)*iFactor;
iCurrNum=(n/iFactor);
iHigherNum=n/(iFactor*10);
switch(iCurrNum)
{
case 0:
iCount+=iHigherNum*iFactor;
break;
case 1:
iCount+=iHigherNum*iFactor+iLowerNum+1;
break;
default:
iCount+=(iHigherNum+1)*iFactor;
break;
}
iFactor*=10;