《编程之美》学习笔记——2.4 1的数目

一、问题

  给定一个十进制正整数N,统计从1开始,到N(含N)的所有整数中出现的所有“1”(包含各个位)的个数。

二、解法

  版本一:最简单的思路,就是从1到N进行遍历,统计逐个数上“1”的个数并相加,最后的结果就是所求的值。

  9 #include <stdlib.h>
 10 #include <stdio.h>
 11 
 12 typedef int TYPE;
 13 TYPE N = 9999;
 14 
 15 TYPE count_number_1(TYPE n)
 16 {
 17     TYPE sum = 0;
 18     while(n) {
 19         sum += n % 10 == 1 ? 1 : 0;
 20         n /= 10;
 21     }
 22     return sum;
 23 }
 24 
 25 int main(void)
 26 {
 27     TYPE i;
 28     TYPE sum = 0;
 29     for(i = 1; i <= N; i++) {
 30         sum += count_number_1(i);
 31     }
 32     printf("sum of number 1 in %d is %d.\n", N, sum);
 33     return EXIT_SUCCESS;
 34 }                                                                               

  特点:逐个数遍历,并利用求余算法统计各个位上“1”的总数。

  时间复杂度计算:O(nlogn)   (对数函数实际上以10为底)

(关于对数时间计算参考维基百科:时间复杂度 對數時間:若演算法的T(n) = O(log n),則稱其具有對數時間。由於計算機使用二進制的記數系統,對數常常以2為底(即log2 n,有時寫作lg n)。然而,由對數的換底公式,loga n和logb n只有一個常數因子不同,這個因子在大O記法中被丟棄。因此記作O(log n),而不論對數的底是多少,是對數時間演算法的標準記法。)

  gprof 时间性能监视测试:

sum of number 1 in 10000000 is 7000001. 时间:0.60(main + count_number_1 )

sum of number 1 in 100000000 is 80000001. 时间:6.43(main + count_number_1 )

sum of number 1 in 1000000000 is 900000001. 时间:71.62(main + count_number_1 )

  版本二:分析数学规律,思考“小数N在每一位上可能出现1的次数”之和来得到结果。

  9 #include <stdlib.h>
 10 #include <stdio.h>
 11 
 12 typedef int TYPE;
 13 TYPE N = 1000000000;
 14 
 15 TYPE main(void)
 16 {   
 17     TYPE i;
 18     TYPE sum = 0;
 19     TYPE bits = 1;
 20     TYPE high_bit_value = 0; 
 21     TYPE current_bit_value = 0;
 22     TYPE low_bit_value = 0;
 23     while(bits <= N) {
 24         high_bit_value = N / (10 * bits);
 25         // current_bit_value = (N % (10 * bits)) / bits;
 26         // 下面赋值参考编程之美例程优化
 27         current_bit_value = (N / bits) % 10;
 28         // low_bit_value = (N % (10 * bits)) % bits;
 29         // 下面赋值参考编程之美例程优化
 30         low_bit_value = N - (N / bits) * bits;
 31         if (0 == current_bit_value)
 32             sum += bits * high_bit_value;
 33         else if(1 == current_bit_value)
 34             sum += bits * high_bit_value + low_bit_value + 1;
 35         else
 36             sum += bits * (high_bit_value + 1);
 37         bits *= 10;
 38     }   
 39     printf("sum of number 1 in %d is %d.\n", N, sum);
 40     return EXIT_SUCCESS;
 41 }                                                                               

  特点:考虑了1-N各个数字之间的关系,对于不同的位数bits的数,可以通过统计每个位上的“1”的次数来求解,这需要找到他们的数学规律,可以采用归纳法来求解。这道题是先把问题转化为数学问题思考,再转回编程实现。下面为思考过程(对于不同位数通过找例子测试得到规律)

对于一位数a(a >= 0):
个位数上的1统计值:
a < 1: A = 0
a >= 1: A = 1
“1”的个数:A
对于二位数ba(b >= 1):
个位数上的1统计值:
a < 1: A = b
a >= 1: A = b + 1
十位数上的1统计值:
b = 1: B = a + 1
b > 1: B = 10
“1”的个数:A + B
对于三位数cba(c >= 1):
个位数上的1统计值:
a < 1: A = cb
a >= 1: A = cb + 1
十位数上的1统计值:
b = 0: B = 10 * c
b = 1: B = 10 * c + a + 1
b > 1: B = 10 * (c + 1)
百位数上的1统计值:
c = 1: ba + 1
c > 1: 100
“1”的个数:A + B + C
对于四位数dcba(d >= 1):
个位数上的1统计值:
a < 1: A = dcb
a >= 1: A = dcb + 1
十位数上的1统计值:
b = 0: B = 10 * dc
b = 1: B = 10 * dc + a + 1
b > 1: B = 10 * (dc + 1)
百位数上的1统计值:
c = 0: C = 100 * d
c = 1: C = 100 * d + ba + 1
c > 1: C = 100 * (d + 1)
千位数上的1统计值:
d = 1: D = cba + 1
d > 1: D = 1000
“1”的个数:A + B + C + D
对于五位数edcba(e >= 1):
个位数上的1统计值:
a = 0: A = 1 * edcb
a = 1: A = 1 * edcb + k + 1 (k = 0)
a > 1: A = 1 * (edcb + 1)
十位数上的1统计值:
b = 0: B = 10 * edc
b = 1: B = 10 * edc + a + 1
b > 1: B = 10 * (edc + 1)
百位数上的1统计值:
c = 0: C = 100 * ed
c = 1: C = 100 * ed + ba + 1
c > 1: C = 100 * (ed + 1)
千位数上的1统计值:
d = 0: C = 1000 * e
d = 1: C = 1000 * e + cba + 1
d > 1: C = 1000 * (e + 1)
万位数上的1统计值:
e = 0: X (不存在)
e = 1: D = 1000 * f + dcba + 1 (f = 0)
e > 1: D = 10000 * (f + 1) (f = 0)
“1”的个数:A + B + C + D + E

 
时间复杂度计算:O(logn)(对数函数实际上以10为底)

  gprof 时间性能监视测试:基本上不耗费时间:0.00

  版本三:逐步缩小数据规模,把大的数分解为更小的数的组合,利用递归的思想求解。

(此想法参考自一篇文章:http://www.cnblogs.com/jy02414216/archive/2011/03/09/1977724.html

  9 #include <stdlib.h>
 10 #include <stdio.h>
 11     
 12 typedef long long TYPE;
 13 TYPE N = 1000000000;
 14     
 15 TYPE count_number_1(TYPE n)
 16 {                                                                               
 17     TYPE sum = 0;
 18     TYPE bits = 0;
 19     TYPE pow_bits = 1;
 20     TYPE n_cpy = n; 
 21     while(n_cpy) {
 22         bits++;
 23         pow_bits *= 10;
 24         n_cpy /= 10;
 25     }   
 26     pow_bits /= 10;
 27     if(bits > 1) {
 28         TYPE top_bit_value = n / pow_bits;
 29         TYPE low_bit_value = n % pow_bits;
 30         // recursive
31         if(top_bit_value == 1) { 
 32             sum = count_number_1(low_bit_value) +
 33                 count_number_1(pow_bits - 1) +
 34                 low_bit_value + 1;
 35         } else {
 36             sum = count_number_1(low_bit_value) +
 37                 top_bit_value * count_number_1(pow_bits - 1) +
 38                 pow_bits;
 39         }       
 40     } else {    
 41         // lowest bits
 42         if (n < 1)
 43             sum = 0;
 44         else
 45             sum = 1;
 46     }   
 47     return sum; 
 48 }   
 49     
 50 int main(void)
 51 {   
 52     TYPE i;
 53     TYPE sum = 0;
 54     sum += count_number_1(N);
 55     printf("sum of number 1 in %lld is %lld.\n", N, sum);
 56     return EXIT_SUCCESS;
 57 }                                                                               

 
时间复杂度计算:O(2^logn)(对数函数实际上以10为底)                  

  gprof 时间性能监视测试:基本上不耗费时间:0.00

  特点:考虑的数据规模的递减,通过递归思路不断缩小数据规模,最后只剩下个位数“1”含有个数的求解。但是数据过大时递归可能会导致堆栈溢出。下面为思考过程(对于不同位数通过找例子测试得到规律):

个位数a:f(a)
a < 1 : A = 0
a >= 1 : A = 1
十位数ba:
b = 1 : BA = f(a) + f(9) + a + 1
b > 1 : BA = f(a) + b * f(9) + 10
百位数cba:
c = 1 : CBA = f(ba) + f(99) + ba + 1
c > 1 : CBA = f(ba) + c * f(99) + 100
千位数dcba:
d = 1 : CBA = f(cba) + f(999) + cba + 1
d > 1 : CBA = f(cba) + d * f(999) + 1000

三、总结与思考

  这道题目的第一种解法是最先想到的解法,符合编程的思维习惯,但是第二种解法确使运算效率提升到一个新的档次,第二种解法,从数学的思考角度去考虑,通过寻找规律来寻找求解。所以,有的复杂的算法,先转变为数学问题,通过数学规律或相互关系寻找新的数学上的解法,再编程实现,效率说不定能够快速提升。当然,第一种解法直观易懂,大家很容易推出问题,第二种解法不容易推出问题,但是效率确实更优的。当程序运算效率跟不上时,不防通过考虑下数学问题推导这种思维进行优化。

  第三种解法参考别的文章的提示思路,采用的时递归缩小数据规模的想法,运用了分治的思想,这个方法实际测试效率时发现也效率和是相当高的,但时间复杂度这个值参考别人的计算结果,并未自己验证,因此无法准确的进行评估。

备注:

1.转载请注明出处:http://blog.csdn.net/chensilly8888/

2.全文源码均开源(在UBUNTU + GCC4.8.2下编译并测试通过),可下载或查看:https://github.com/chenxilinsidney/funnycprogram/tree/master/beauty_of_programming/count_number_1

3.有写错或写漏之处请指正,谢谢!

 

    原文作者:chensilly8888
    原文地址: https://blog.csdn.net/chensilly8888/article/details/42213437
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞