从0开始到某个数N有点多少个1——编程之美2.4
2011-05-04 10:32
204 查看
题目:
给定一个十进制正整数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。
问题是:
1.
写一个函数f(N),返回1到N之间出现的“1”的个数,比如f(12)=5。
2.
在32位整数范围内,满足条件“f(N)=N”的最大的N是多少?
分析与解法
问题1的解法一
这个问题看上去并不是一个困难的问题,因为不需要太多的思考,我想大家都能找到一个最简单的方法来计算f(N),那就是从1开始遍历到N,将其中每一个数中含有“1”的个数加起来,自然就得到了从1到N所有“1”的个数的和。
这个方法很简单,只要学过一点编程知识的人都能想到,实现也很简单,容易理解。但是这个算法的致命问题是效率,它的时间复杂度是O(N)*计算一个整数数字里面“1”的个数的复杂度=O(N*logN)
如果给定的N比较大,则需要很长的运算时间才能得到计算结果。比如在笔者的机器上,如果给定N=100 000
000,则算出f(N)大概需要40秒的时间,计算时间会随着N的增大而线性增长。看起来要计算从1到N的数字中所有1的和,至少也得遍历1到N之间所有的数字才能得到。那么能不能找到快一点的方法来解决这个问题呢?要提高效率,必须摒弃这种遍历1到N所有数字来计算f(N)的方法,而应采用另外的思路来解决这个问题。
问题1的解法二
仔细分析这个问题,给定了N,似乎就可以通过分析“小于N的数在每一位上可能出现1的次数”之和来得到这个结果。让我们来分析一下对于一个特定的N,如何得到一个规律来分析在每一位上所有出现1的可能性,并求和得到最后的f(N)。
先从一些简单的情况开始观察,看能不能总结出什么规律。
先看1位数的情况。
如果N=3,那么从1到3的所有数字:1、2、3,只有个位数字上可能出现1,而且只出现1次,进一步可以发现如果N是个位数,如果N>=1,那么f(N)都等于1,如果N=0,则f(N)为0。
再看2位数的情况。
如果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(123)=个位出现1的个数+十位出现1的个数+百位出现1的次数=13+20+24=57;
同理我们可以再分析4位数、5位数。
读者朋友们可以写一写,总结一下各种情况有什么不同。
根据上面的一些尝试,下面我们推导出一般情况下,从N得到f(N)
的计算方法:
假设N=abcde,这里a、b、c、d、e分别是十进制数N的各个位数上的数字。如果要计算百位上出现1的次数,它将会受到三个因素的影响:百位上的数字,百位以下(低位)的数字,百位(更高位)以上的数字。
如果百位上的数字为0,则可以知道,百位上可能出现1的次数由更高位决定,比如12013,则可以知道百位出现1的情况可能是100~199,1
100~1 199,2 100~2 199,…,11 100~11 199,一共有1200个。也就是由更高位数字(12)决定,并且等于更高位数字(12)*当前位数(100)。
如果百位上的数字为1,则可以知道,百位上可能出现1的次数不仅受更高位影响,还受低位影响,也就是由更高位和低位共同决定。例如对于12113,受更高位影响,百位出现1的情况是100~199,1
100~1 199,2 100~2 199,…,11 100~11
199,一共有1200个,和上面第一种情况一样,等于更高位数字(12)*当前位数(100)。它还受低位影响,百位出现1的情况是12 100~12
113,一共114个,等于低位数字(113)+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,一共有1300个,并且等于更高位数字+1(12+1)*当前位数(100)。
通过上面的归纳和总结,我们可以写出如下的更高效算法来计算f(N):
其算法思路是:通过对数字进行有规律的总结,发现从1到N,中出现的所有的1的总数。可以从N这个数总结出来的。
那么出现1的总数应该等于,个位上出现1的次数+十位上出现1的次数+百位上出现1的次数+。。。。。。
所以对于一个数abcde,取百位上的c来计算,
假若c是"1",那么百位上1的个数是由他的高位和低位来决定的。等于ab*100+cde+1;
假若c是"0",那么百位上1的个数是ab*100;
假如c是大于1,那么
百位上1的个数是(ab+1)*100;
给定一个十进制正整数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。
问题是:
1.
写一个函数f(N),返回1到N之间出现的“1”的个数,比如f(12)=5。
2.
在32位整数范围内,满足条件“f(N)=N”的最大的N是多少?
分析与解法
问题1的解法一
这个问题看上去并不是一个困难的问题,因为不需要太多的思考,我想大家都能找到一个最简单的方法来计算f(N),那就是从1开始遍历到N,将其中每一个数中含有“1”的个数加起来,自然就得到了从1到N所有“1”的个数的和。
这个方法很简单,只要学过一点编程知识的人都能想到,实现也很简单,容易理解。但是这个算法的致命问题是效率,它的时间复杂度是O(N)*计算一个整数数字里面“1”的个数的复杂度=O(N*logN)
如果给定的N比较大,则需要很长的运算时间才能得到计算结果。比如在笔者的机器上,如果给定N=100 000
000,则算出f(N)大概需要40秒的时间,计算时间会随着N的增大而线性增长。看起来要计算从1到N的数字中所有1的和,至少也得遍历1到N之间所有的数字才能得到。那么能不能找到快一点的方法来解决这个问题呢?要提高效率,必须摒弃这种遍历1到N所有数字来计算f(N)的方法,而应采用另外的思路来解决这个问题。
问题1的解法二
仔细分析这个问题,给定了N,似乎就可以通过分析“小于N的数在每一位上可能出现1的次数”之和来得到这个结果。让我们来分析一下对于一个特定的N,如何得到一个规律来分析在每一位上所有出现1的可能性,并求和得到最后的f(N)。
先从一些简单的情况开始观察,看能不能总结出什么规律。
先看1位数的情况。
如果N=3,那么从1到3的所有数字:1、2、3,只有个位数字上可能出现1,而且只出现1次,进一步可以发现如果N是个位数,如果N>=1,那么f(N)都等于1,如果N=0,则f(N)为0。
再看2位数的情况。
如果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(123)=个位出现1的个数+十位出现1的个数+百位出现1的次数=13+20+24=57;
同理我们可以再分析4位数、5位数。
读者朋友们可以写一写,总结一下各种情况有什么不同。
根据上面的一些尝试,下面我们推导出一般情况下,从N得到f(N)
的计算方法:
假设N=abcde,这里a、b、c、d、e分别是十进制数N的各个位数上的数字。如果要计算百位上出现1的次数,它将会受到三个因素的影响:百位上的数字,百位以下(低位)的数字,百位(更高位)以上的数字。
如果百位上的数字为0,则可以知道,百位上可能出现1的次数由更高位决定,比如12013,则可以知道百位出现1的情况可能是100~199,1
100~1 199,2 100~2 199,…,11 100~11 199,一共有1200个。也就是由更高位数字(12)决定,并且等于更高位数字(12)*当前位数(100)。
如果百位上的数字为1,则可以知道,百位上可能出现1的次数不仅受更高位影响,还受低位影响,也就是由更高位和低位共同决定。例如对于12113,受更高位影响,百位出现1的情况是100~199,1
100~1 199,2 100~2 199,…,11 100~11
199,一共有1200个,和上面第一种情况一样,等于更高位数字(12)*当前位数(100)。它还受低位影响,百位出现1的情况是12 100~12
113,一共114个,等于低位数字(113)+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,一共有1300个,并且等于更高位数字+1(12+1)*当前位数(100)。
通过上面的归纳和总结,我们可以写出如下的更高效算法来计算f(N):
其算法思路是:通过对数字进行有规律的总结,发现从1到N,中出现的所有的1的总数。可以从N这个数总结出来的。
那么出现1的总数应该等于,个位上出现1的次数+十位上出现1的次数+百位上出现1的次数+。。。。。。
所以对于一个数abcde,取百位上的c来计算,
假若c是"1",那么百位上1的个数是由他的高位和低位来决定的。等于ab*100+cde+1;
假若c是"0",那么百位上1的个数是ab*100;
假如c是大于1,那么
百位上1的个数是(ab+1)*100;
#include <iostream> using namespace std; typedef int Type; Type Count1InAInteger(Type n) { Type iNum=0; while(n!=0) { iNum+=(n%10==1)?1:0; n/=10; } return iNum; } Type f(Type n) { Type iCount=0; for(Type i=1;i<=n;i++) { iCount+=Count1InAInteger(i); } return iCount; } Type Sum(Type n) { Type Count = 0; Type Factor = 1; Type LowerNum = 0; Type CurrNum = 0; Type HigherNum = 0; while(n/Factor != 0) { LowerNum = n - (n / Factor) * Factor; CurrNum = (n / Factor)%10; HigherNum = n /(Factor *10); switch(CurrNum) { case 0: Count += HigherNum * Factor; break; case 1: Count += HigherNum * Factor + LowerNum +1 ; break; default: Count += (HigherNum + 1) * Factor; break; } Factor *= 10; } return Count; } int main() { Type num; cin>> num; cout << Sum(num) << endl; cout << f(num) << endl; return 1; }
相关文章推荐
- 从0开始到某个数N有点多少个1
- 《幽游白书》——多少'罪恶'由汝开始
- 031 二进制中1的个数(keep it up, 看到这个题刚开始有点蒙)
- 有N对左右括号,问合法排列有多少种?对解法有点不理解求教!
- 031 二进制1的数量(keep it up, 看到这个问题,刚开始有点蒙)
- 从A点开始到B点通过最短的路一共有多少条
- macbook 新手可以参考下(随记的常用操作,很乱,多少有点用)
- 兔子繁殖问题,设有一对新生兔子,从第四个月开始他们每个月月初都生一对兔子 新生的兔子从第四个月月初开始又每个月生一对兔子 按此规律,并假定兔子没有死亡,n(n <= 20)个月月末共有多少对兔子?
- IOS8 设置TableView Separatorinset 分割线从边框顶端开始 (这个和我之前的有点区别)
- java 使用Calendar类计算每月有多少自然周,并输出每周的开始和结束日期
- 编程之美:第四章 数字之趣 4.9数独知多少
- 有点发烧,早点睡觉了,明天某某大学也开始正式放假了:)
- 小明的妈妈每天会给他20元零花钱。平日里,小明先花掉一半,再把一半存起来 * 每到周日,小明拿到钱后会把所有零花钱花掉一半。请编程计算,从周一开始,小明需要多少天才能存够100元?
- Description 对于一本共有n页的书,它的页码从1开始编号,一直到n,页码中没有无用的0,则该书页码中分别共用到了多少个0,1,2,3,4,5,6,7,8,9?
- 结束时间减去开始时间用了多少小时
- 编程之美2.4——1的数目
- 开始工作,总得有点东西留下
- 一个有序数列,序列中的每一个值都能够被2或者3或者5所整除,这个序列的初始值从1开始,但是1并不在这个数列中。求第1500个值是多少?
- 计算一年有多少个周,每个周的开始日期和结束日期是什么
- 开始有点伤心但是希望还在