十进制整数区间[1, N]中数字1出现的个数
2010-08-16 16:24
162 查看
毫无疑问,数字历来都是高深的科学家和日常小市民谈论的对象,著名的哥德巴赫猜想便是举世瞩目的数字难题,而人们在超市购物总喜欢计较价钱中的一角半块。《编程之美》中一节有如下两个数字问题:
问题一:定义函数f(N)表示十进制整数区间[1,N]中数字1出现的个数,利用程序实现函数f(N)。
问题二:是否存在满足f(N)=N的最大整数Nmax?进行简单分析。
问题需要一一解决,不能操之过急,往往脑海中第一个闪现的方法效率并不高,三思之后你会发现换条路走其实更快。世界的本质或许并不复杂,平时遇到的问题大多比较简单;而世界或许又不如此单纯得简单,以至于人们对不解之谜进行孜孜以求的探索。
先来解决问题一。f(N)到底是什么呢?给出简单的特例:当N=2时,f(N)是整数序列{1,2中数字1出现的个数,即f(N)=f(2)=1;当N=12时,f(N)是整数序列{1,2,…,11,12}中数字1出现的个数,即f(N)=f(12)=5。没错,仅仅是计算1的个数,无论1在个位还是在十位,或是百位。要解决的问题很简单吧!下面我们选用C/C++语言解决这个问题。
问题当然是解决了,然而总觉得这种穷举法是最愚笨的方法,为下下之策,其时间复杂度为O(Nlog10N)=O(Nlog2N)。
有没有更好的办法呢?经过一阵思考后,在脑海中我将从1到N的整数按序形成一个N×1且元素右对齐的矩阵,然后分析个位、十位、百位等位上数字1出现的规律。结果出来了:个位上每10个数有一个1;十位上,每100个数有10个1;百位上,每1000个数有100个1……没错,就是这样优美!生活中很多事情虽然简单,但简单中却总存在着优美的规律,能够发现这些美的人便会享受生活。同样,真正的数学家总是能够发现并享受到数学之美,他们的心与数学融为一体,而这样的人很少,所以真正的数学家很少。当然,我们周围总存在一些数学专业的毕业生自称可以感受到数学之美,若果真如此,中国的数学大师应该令世界羡慕不已,可惜的是当前中国的高校尚未培养出真正的大师,因此谦逊的我们并不应妄自菲薄。闲话少说,接着上面的规律讨论。既然有如此周期性的规律,那么对于给定的N,只要得到有多少个位、十位、百位等便可以利用周期性数值得到答案。当然,实际程序设计中因该考虑更具体的特例,例如,N=12时,个位有两个1,十位仅有一个1;当N=22时,个位有三个1,十位确有十个1。
问题一详细的分析可参见《编程之美》相关内容。对于L位整数N,按其第k位数值X将N分解为三部分:X的低位XL=Nmod(10k-1)、第k位数值X=rounddown(N/10k-1) mod(10)、X的高位XH=rounddown(N/10k),其中mod()为取余操作,rounddown()为向下取整操作。如此,分类讨论如下:
当X=0时,f(N)=XH*10k-1;
当X=1时,f(N)=XH*10k-1+XL+1;
当2≤X≤9时,f(N)=(XH+1)10k-1。
如下C/C++代码按照上述规律可以更加有效地解决问题一,时间复杂度为O(log10N)=O(log2N),较之简单的穷举法大大地降低。
下面来分析讨论问题二的解决方法。对于问题二,我们需要回答是否存在满足f(N)=N的最大整数Nmax。首先要理解的是f(N)=N,如f(1)=1。因此,存在满足f(N)=N的整数N,且这样的整数最小为1。接下来,按照特例进行分析 的变化规律:
f(9)=1
f(99)=20
f(999)=300
……
f(999999999)=900000000
f(9999999999)=10000000000
由此得到归纳公式:f(10N-1)=N*10N-1。特例分析中当N=9999999999,f(N)>N。归纳公式可猜想:当N大于某一整数ε时,始终有f(N)>N。事实上确实如此,具体证明可参见《编程之美》,在此不再赘述。至此问题二得到了最终答案,即[1,ε]中存在满足f(N)=N的最大整数Nmax,ε是满足f(N)=N的整数N集合的上界,而N=1011-1=9999999999便是这样一个上界。用程序计算满足f(N)=N的最大整数Nmax十分简单,只需要对上述计算f(N)的程序进行修改,使的N从N=1011-1=9999999999递减并检查条件f(N)=N即可。
至此,两个问题得到全部解决。我们发现,对于给定的问题,只要针对特定要求进行仔细分析,便可抽丝剥茧地一步步解决问题。往往第一答案并不是最佳的,而大多数时候有必要进行认真分析以寻求更优的加法以减少时间复杂度,而不能简单地依靠高性能计算机。数学家可以发现数学之美,我们在解决问题的过程中也可以发现待解决问题中的规律之美,二者似乎冥冥中总有些许联系,至于此等联系恐怕便只能意会而不便言传了。
问题一:定义函数f(N)表示十进制整数区间[1,N]中数字1出现的个数,利用程序实现函数f(N)。
问题二:是否存在满足f(N)=N的最大整数Nmax?进行简单分析。
问题需要一一解决,不能操之过急,往往脑海中第一个闪现的方法效率并不高,三思之后你会发现换条路走其实更快。世界的本质或许并不复杂,平时遇到的问题大多比较简单;而世界或许又不如此单纯得简单,以至于人们对不解之谜进行孜孜以求的探索。
先来解决问题一。f(N)到底是什么呢?给出简单的特例:当N=2时,f(N)是整数序列{1,2中数字1出现的个数,即f(N)=f(2)=1;当N=12时,f(N)是整数序列{1,2,…,11,12}中数字1出现的个数,即f(N)=f(12)=5。没错,仅仅是计算1的个数,无论1在个位还是在十位,或是百位。要解决的问题很简单吧!下面我们选用C/C++语言解决这个问题。
#include <iostream> using namespace std; unsigned int Count1InAInteger(unsigned int n); unsigned int f(unsigned int n); int main(int argc, char* argv[]) { unsigned int n = 0; cin>>n; // Quit if input 0 while (n > 0) { cout<<"f(n) = f("<<n<<") = "<<f(n)<<endl; cin>>n; } return 0; } unsigned int Count1InAInteger(unsigned int n) { unsigned int iNum = 0; while(n != 0) { iNum += (n % 10 == 1) ? 1 : 0; n /= 10; } return iNum; } unsigned int f(unsigned int n) { unsigned int iCount = 0; for (unsigned int i = 1; i <= n; i++) { iCount += Count1InAInteger(i); } return iCount; }
问题当然是解决了,然而总觉得这种穷举法是最愚笨的方法,为下下之策,其时间复杂度为O(Nlog10N)=O(Nlog2N)。
有没有更好的办法呢?经过一阵思考后,在脑海中我将从1到N的整数按序形成一个N×1且元素右对齐的矩阵,然后分析个位、十位、百位等位上数字1出现的规律。结果出来了:个位上每10个数有一个1;十位上,每100个数有10个1;百位上,每1000个数有100个1……没错,就是这样优美!生活中很多事情虽然简单,但简单中却总存在着优美的规律,能够发现这些美的人便会享受生活。同样,真正的数学家总是能够发现并享受到数学之美,他们的心与数学融为一体,而这样的人很少,所以真正的数学家很少。当然,我们周围总存在一些数学专业的毕业生自称可以感受到数学之美,若果真如此,中国的数学大师应该令世界羡慕不已,可惜的是当前中国的高校尚未培养出真正的大师,因此谦逊的我们并不应妄自菲薄。闲话少说,接着上面的规律讨论。既然有如此周期性的规律,那么对于给定的N,只要得到有多少个位、十位、百位等便可以利用周期性数值得到答案。当然,实际程序设计中因该考虑更具体的特例,例如,N=12时,个位有两个1,十位仅有一个1;当N=22时,个位有三个1,十位确有十个1。
问题一详细的分析可参见《编程之美》相关内容。对于L位整数N,按其第k位数值X将N分解为三部分:X的低位XL=Nmod(10k-1)、第k位数值X=rounddown(N/10k-1) mod(10)、X的高位XH=rounddown(N/10k),其中mod()为取余操作,rounddown()为向下取整操作。如此,分类讨论如下:
当X=0时,f(N)=XH*10k-1;
当X=1时,f(N)=XH*10k-1+XL+1;
当2≤X≤9时,f(N)=(XH+1)10k-1。
如下C/C++代码按照上述规律可以更加有效地解决问题一,时间复杂度为O(log10N)=O(log2N),较之简单的穷举法大大地降低。
#include <iostream> using namespace std; unsigned int Sum1s(unsigned int n); int main(int argc, char* argv[]) { unsigned int n = 0; cin>>n; // Quit if input 0 while (n > 0) { cout<<"f(n) = f("<<n<<") = "<<Sum1s(n)<<endl; cin>>n; } return 0; } unsigned int Sum1s(unsigned int n) { unsigned int iCount = 0; unsigned int iFactor = 1; unsigned int iLowerNum = 0; unsigned int iCurrNum = 0; unsigned int iHigherNum = 0; while(n / iFactor != 0) { iLowerNum = n - (n / iFactor) * 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; }
下面来分析讨论问题二的解决方法。对于问题二,我们需要回答是否存在满足f(N)=N的最大整数Nmax。首先要理解的是f(N)=N,如f(1)=1。因此,存在满足f(N)=N的整数N,且这样的整数最小为1。接下来,按照特例进行分析 的变化规律:
f(9)=1
f(99)=20
f(999)=300
……
f(999999999)=900000000
f(9999999999)=10000000000
由此得到归纳公式:f(10N-1)=N*10N-1。特例分析中当N=9999999999,f(N)>N。归纳公式可猜想:当N大于某一整数ε时,始终有f(N)>N。事实上确实如此,具体证明可参见《编程之美》,在此不再赘述。至此问题二得到了最终答案,即[1,ε]中存在满足f(N)=N的最大整数Nmax,ε是满足f(N)=N的整数N集合的上界,而N=1011-1=9999999999便是这样一个上界。用程序计算满足f(N)=N的最大整数Nmax十分简单,只需要对上述计算f(N)的程序进行修改,使的N从N=1011-1=9999999999递减并检查条件f(N)=N即可。
至此,两个问题得到全部解决。我们发现,对于给定的问题,只要针对特定要求进行仔细分析,便可抽丝剥茧地一步步解决问题。往往第一答案并不是最佳的,而大多数时候有必要进行认真分析以寻求更优的加法以减少时间复杂度,而不能简单地依靠高性能计算机。数学家可以发现数学之美,我们在解决问题的过程中也可以发现待解决问题中的规律之美,二者似乎冥冥中总有些许联系,至于此等联系恐怕便只能意会而不便言传了。
相关文章推荐
- 计算在区间 1 到 n 的所有整数中,数字 x(0 ≤ x ≤ 9)共出现了多少次?
- 给定一个十进制正整数N,求出从1开始,到N的所有整数,数字1出现的次数(java实现)
- 求出1~13的整数中1出现的次数,并算出100~1300的整数中1出现的次数?为此他特别数了一下1~13中包含1的数字有1、10、11、12、13因此共出现6次,但是对于后面问题他就没辙了。ACMer希望你们帮帮他,并把问题更加普遍化,可以很快的求出任意非负整数区间中1出现的次数。
- 【C代码练习17】输入数量不确定的0-9范围内的整数,统计每一种数字出现的次数,输入以-1结束
- 一组无序的整数找出出现次数大于一半的数字
- 数据结构——算法之(016)( 输入整数n,计算从1到n这n个整数的十进制表示中1出现的次数和)
- C编写程序数一下 1到 100 的所有整数中出现多少次数字9
- 编写程序数一下 1到 100 的所有整数中出现多少次数字 9
- 算法题:找出整数数组中两个只出现一次的数字
- 计算数值区间内某个数字出现的次数
- 随机产生50个整数,位于[10,50],统计每个数字出现的次数以及出现次数最多的数字与出现次数并打印.如果出现次数为0不打印,打印时要求升序
- C语言:编写程序数一下 1到 100 的所有整数中出现多少次数字 9
- Uva1225 求数字0-9在前n个正整数中出现的次数(1<=n<=10000)
- 输入一个整数n,求从1到n这n个整数的十进制表示中1出现的次数。
- 整数数组,一个数字出现了半数以上次,找出这个数字
- 输入一个整数n,求从1到n这n个整数的十进制表示中1出现的次数
- 查找介于n1与n2(0<n1<n2<32768)之间所有满足下列条件的整数: (1)该数的十进制表示中有且仅有两个相同的数字位; (2)该数是素数。
- 第十二节 机试题目之十进制1~N的所有整数中出现“1”的个数
- 求一个区间[a,b]中数字1出现的次数
- (算法)从0到n整数中数字2出现的次数