正文

"1"的数目2011-08-17 12:12:00

【评论】 【打印】 【字体: 】 本文链接:http://blog.pfan.cn/sisheng/52743.html

分享到:

"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。 问题是: 1. 写一个函数f(N),返回1到N之间出现的“1”的个数,比如f(12)=5。 2. 在32位整数范围内,满足条件“f(N)= N”的最大的N是多少?   【问题1 的解法一】 这个问题看上去并不是一个困难的问题,因为不需要太多的思考,我想大家都能找到一个最简单的方法来计算f(N),那就是从1 开始遍历到N,将其中每一个数中含有“1”的个数加起来,自然就得到了从1 到N 所有“1”的个数的和。写成程序如下: public class OneToN {         // 计算某一个数中 1 的个数     private long Count1InAInteger(long n)     {         long iNum = 0;         while (n != 0)        {             iNum += (n % 10 == 1) ? 1 : 0;             n /= 10;         }         return iNum;     }     // 计算从 1 到 n 中 1 的个数     public long f(long n)     {                 long iCount = 0;          for (long i = 1; i <= n; i++)         {             iCount += Count1InAInteger(i);         }         return iCount;        } } class Program {      static void Main(string[] args)      {           OneToN ot = new OneToN();           long count = ot.f(12);           Console.WriteLine(count);      } } 复制代码 这个方法很简单,只要学过一点编程知识的人都能想到,实现也很简单,容易理解。但是这个算法的致命问题是效率,它的时间复杂度是 O(N)×计算一个整数数字里面“1”的个数的复杂度 = O(N * log2 N)如果给定的 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(23)= 个位出现1 的个数 + 十位出现1 的个数 + 百位出现1 的次数 = 13 + 20 + 24 = 57; 同理我们可以再分析 4 位数、5 位数。读者朋友们可以写一写,总结一下各种情况有什么不同。   根据上面的一些尝试,下面我们推导出一般情况下,从 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)。

阅读(1724) | 评论(0)


版权声明:编程爱好者网站为此博客服务提供商,如本文牵涉到版权问题,编程爱好者网站不承担相关责任,如有版权问题请直接与本文作者联系解决。谢谢!

评论

暂无评论
您需要登录后才能评论,请 登录 或者 注册