问题描述
给定一个十进制正整数N,求出从1开始,到N的所有整数中出现的所有“1”的个数。
思路一
这个问题初一看感觉就是不是很难,因为马上就能想到一个最简单的方法来计算了,那就是从1开始遍历到N,将其
中每一个数中含有的“1”的个数加起来,这样便得到了从1到N所有“1”的个数的和。下面是实现代码
#include<iostream>
using namespace std;
int count1(int n) //计算整数n中“1”的个数
{
int count = 0;
while (n)
{
count += (n % 10 == 1) ? 1 : 0;
n /= 10;
}
return count;
}
int main()
{
long long n;
long long sum;
while (cin >> n)
{
sum = 0;
for (long long i = 1; i <= n; i++)//遍历1到N
{
sum += count1(i);
}
cout << sum << endl;
}
return 0;
}
这个方法很简单吧!相信大家都能想到,而且代码的实现也比较简单,容易理解。但是这个算法确有一个致命的问
题,那就是效率问题,它的时间复杂度是O(N*log2N)。如果给定的N较大的话,则需要很长的运算时间才能得出
结果。在我自己的电脑上算100 000 000,大概用了17秒多点。而且计算时间会随着N的增大而线性增大。
这样的效率显然不符合实际要求的,那么就得想办法进行优化了,显然这个算法效率低下的最终原因还是总是需要
从1遍历到N,那么想要提高效率就必须得摈弃这种遍历方法啦。下面来看看编程之美上的思路吧,果然很犀利!
思路二
仔细分析这个问题,给定了N,似乎就可以通过分析“小于N等于的数在每一位上可能出现1的次数”之和来得到这个结
果。让我们先来分析一些特定的N,如何得到一个规律来分析在每个位上所有出现1的可能性,并求出得到最后的结
果。先从一些简单的情况开始观察,看看能不能发现什么规律。
先看N为1位数的情况:
如果N = 3,那么从1到3的所有数字:1、2、3,只有一位数上可能出现1,而且只有一次,进一步可以发现如果N是
个位数,如果N>=1,那么总数都等于1,如果N=0,则总数为0。
再看N为2位数的情况:
如果N = 12,那么从1到12的所有数字:1、2、3、4、5、6、7、8、9、10、11、12,个位和十位的数字上都可能有
1,我们可以将它们分开来考虑,个位出现1的次数有两次:1和11,十位出现1的次数有3次:10、11、12,所以总数
为5。要注意的是11这个数字在十位和个位都出现了1,但是11恰好在个位为1和十位为1中被计算了两次,所以不用
特殊处理,是对的。在考虑23的情况,它和N=12有点不同,十位出现1的次数为10次,从10到19,个位出现1的次数
为1、11、21,所以总数为13。通过对两位数进行分析,我们发现,个位数出现1的次数不仅和个位数字有关,还和
十位数字有关。。。。。。(此次省略N字!!!)
上面省略的是编程之美上的接上的所有分析,这里就不在累赘啦。如果需要编程之美电子书的朋友可以给我留言,到
时直接发过来!
其实看到上面的分析基本已经知道了,这里采用的方法是先通过分析一些数据,找出N的各个数位上出现1的个数,然
后把这些加起来,就得到了最后的结果啦。大家自己在纸上写下,可以很容易知道,每个位上出现1的次数受4个因素
的影响:当前所在位数、比当前位更高位的数iHigherNum、当前位的数iCurrNum、比当前位更地位的iLowerNum。
比如N=2051,当计算十位上出现1的次数时,iHigherNum=20,iCurrNum=5,iLowerNum=3。接下来就是要找出这个规
律啦。就拿2051来说吧:
我们先来看看十位上出现1的情况吧:10-19、110-119......2010-2019,总数=10*21;
再看看百位上的情况:100-199、1100-1199,总数=100*2;
再看个位上的情况,对于最高(低)位有点不同,因为最高(低)位的更高(低)位没有,所以这里设为0:1、11、
21、31......2051,总数=206*1;
最后看下千位上的情况:1000-1999,总数=1*1000。
最后从1到N中所有出现1的次数=210+200+206+1000=1616。
我在纸上另外测试了很多数据,最后得出了规律,下面F(n)表示数字N从最低位开始第n位上1出现的次数,k表示10的
n次方。
iCurrNum = 0时,F(n) = iHigherNum * k;
iCurrNum = 1时,F(n) = iHigherNum * k + iLower + 1;
iCurrNum > 1时,F(n) = (iHigher+1) * k。
由此得出的算法实现代码如下:
#include<iostream>
using namespace std;
long long count1 (int n)
{
long long iCount = 0;
long long iFactor = 1;
long long iLowerNum = 0;
long long iHigherNum = 0;
long long iCurrNum = 0;
while (n / iFactor)
{
iLowerNum = n % iFactor;
iCurrNum = n / iFactor % 10;
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;
}
return iCount;
}
int main()
{
long long n;
int sum;
while (cin >> n)
{
sum = count1(n);
cout << sum << endl;
}
}
这个程序计算100 000 000在我机子上运行不到1毫秒就搞定啦,速度不知道提高了多少倍!