一、問題
給定乙個十進位制正整數n,統計從1開始,到n(含n)的所有整數中出現的所有「1」(包含各個位)的個數。
二、解法
版本一:最簡單的思路,就是從1到n進行遍歷,統計逐個數上「1」的個數並相加,最後的結果就是所求的值。
9 #include 10 #include 11
12 typedef int type;
13 type n = 9999;
14 15 type count_number_1(type n)
16 22 return sum;
23 }
24 25 int main(void)
26 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 時間效能監視測試:
版本二:分析數學規律,思考「小數n在每一位上可能出現1的次數」之和來得到結果。
9 #include 10 #include 11
12 typedef int type;
13 type n = 1000000000;
14 15 type main(void)
16
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為底)
版本三:逐步縮小資料規模,把大的數分解為更小的數的組合,利用遞迴的思想求解。
(此想法參考自一篇文章:
9 #include 10 #include 11
12 typedef long long type;
13 type n = 1000000000;
14
15 type count_number_1(type n)
16
26 pow_bits /= 10;
27 if(bits > 1) else
40 } else
47 return sum;
48 }
49
50 int main(void)
51
時間複雜度計算:o(2^logn)(對數函式實際上以10為底)
gprof 時間效能監視測試:
特點:考慮的資料規模的遞減,通過遞迴思路不斷縮小資料規模,最後只剩下個位數「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
三、總結與思考
這道題目的第一種解法是最先想到的解法,符合程式設計的思維習慣,但是第二種解法確使運算效率提公升到乙個新的檔次,第二種解法,從數學的思考角度去考慮,通過尋找規律來尋找求解。所以,有的複雜的演算法,先轉變為數學問題,通過數學規律或相互關係尋找新的數學上的解法,再程式設計實現,效率說不定能夠快速提公升。當然,第一種解法直觀易懂,大家很容易推出問題,第二種解法不容易推出問題,但是效率確實更優的。當程式運算效率跟不上時,不防通過考慮下數學問題推導這種思維進行優化。
第三種解法參考別的文章的提示思路,採用的時遞迴縮小資料規模的想法,運用了分治的思想,這個方法實際測試效率時發現也效率和是相當高的,但時間複雜度這個值參考別人的計算結果,並未自己驗證,因此無法準確的進行評估。
3.有寫錯或寫漏之處請指正,謝謝!
程式設計之美2 4 1的數目
總體思路 先求個位上出現的1的個數,再找十位再找百位。先看個位找找規律 5 05 1 1 15 2 1,11 25 3 1,11,21 35 4 1,11,21,31 325 33 1,11,21,31,41,301,311,321 結論 個位上的1的數目s n 10 1 再看十位,找找規律 25 ...
程式設計之美 2 4 「1的數目」
擴充套件問題 二進位制數n,從1開始,到n的所有二進位制數,然後數下其中出現的所有 1 的個數。例如 f 1 1 f 10 2 因為01,10共有2個1 f 11 100 因為01,10,11共有4個1 思路 以10110為例,考慮最高位為0時共有幾個1,因為後面4位中1和0出現的概率是相等的,也即...
程式設計之美 2 4 1的數目
package code.beauty.fungame public class countone private static int count1inaint int n return num private static void count1inallint int n system.out...