設計乙個演算法,計算出n階乘中尾部零的個數先說結論,此問題大致有三種思路:第一種算出結果,然後檢視末尾的0的個數,效果非常差;第二種,加法操作,從5開始,每次進5,然後判斷,效果達不到o(logn);第三種,每次除5,多次之後結束。樣例11! = 39916800,因此應該返回 2
挑戰o(logn)的時間複雜度
詳情如下。
重點分析在演算法2和演算法3,需要的可以直接跳到這部分檢視。
面對此問題,第一反應是直接計算結果:11!=39916800,然後設計程式判斷末尾的0的個數,很簡單就可以實現。
但是相應的會有很多的問題:
1、計算階乘的開銷
現在只是11的階乘,都已經很大了,如果是5555550000000
的階乘呢?按照程式的計算結果,末尾會有1388887499996
個0,計算開銷很值得考慮。
2、溢位
按照上面的介紹,5555550000000
的階乘有1388887499996
個0,那麼可以推知階乘的結果會是很大的乙個整數,肯定會超出long型別的界限,結果會溢位。這樣還要考慮處理溢位問題,又是另乙個問題。
3、效率
演算法2會涉及到效率問題,會發現即使是演算法2也會出現計算時間超出要求的問題,那麼更為「樸素」的演算法1效率更是可想而知了。
因此,演算法1,捨棄。
演算法2分析
仔細的考慮問題,會發現末尾出現的0是10或10的倍數相乘的結果,而10其實是5與偶數相乘。也就是,最終結果中末尾出現的0是5、10、15、20、25…自身或與偶數相乘之後的產生的。下面可以分為偶數和5的倍數分析。
首先考慮偶數。
考慮2的冪次項2、4、8…中的2的個數,發現2的冪指數的增長速度遠比5的冪指數增長的快,更不用說其他的普通偶數6、12、14…。因此可以認為有足夠的偶數與奇數形式的5的倍數相乘產生足夠的0。所以我們後面只考慮5的倍數。
接著考慮5的倍數。
1、2、3、4、5、6、7、8、9、10、11...
其實1、2、3、4、6、7…都是可以不用考慮的,因此選擇以5為迭代步數即可。
首先,這些數字都可以不用進行%5
(對5取餘數)運算,因此每次迴圈時可以直接將函式的count
變數直接加1。其次,考慮25、125、625…等5的冪次項,因為他們每乙個都可以在與偶數相乘之後產生多個0。因此,設定乙個迴圈體,判斷是多少冪次項,並將結果加進count
。
綜上所述,可以編寫**如下:
演算法2**
public
class
solution
}return count;}}
**很簡單,不再解釋。
但是效率很差,分析發現**的時間複雜度實際是o(n/5)~=o(n),達不到要求的o(logn)。
演算法2雖然可以解決問題,但考慮執行效率,演算法2應該捨棄。
反思&對比
這個演算法真的是感觸很深,對平時很多習以為常的公式、道理有了非常直觀的認識,因此對自己的衝擊很大,也促進了思考的進步。
提交演算法2的**,發現前面的簡單測試都能通過,但是數值5555550000000
測試失敗。特別是實現了時間複雜度o(logn)的演算法3之後,才發現兩者時間開銷差別真的是很大。
重新分析
1、2、3、4、5、6、7、8、9、10、11、...
1、分析上面的數列可知,每5個數中會出現乙個可以產生結果中0的數字。把這些數字抽取出來是:
...、5、...、10、...、15、...、20、...、25、...
這些數字其實是都能滿足5*k
的數字,是5的倍數。統計一下他們的數量:n1=n/5
。比如如果是101,則101之前應該是5,10,15,20,...,95,100
共101/5=20
個數字滿足要求。
整除操作滿足上面的數量統計要求。2、將
1
中的這些數碼化成5*(1、2、3、4、5、...)
的形式,內部的1、2、3、4、5、...
又滿足上面的分析:每5個數字有乙個是5的倍數。抽取為:
...、25、...、50、...、75、...、100、...、125、...
而這些數字都是25的倍數(5的2次冪的倍數),自然也都滿足5*k
的要求。
這些數字是25、50、75、100、125、...=5*(5、10、15、20、25、...)=5*5*(1、2、3、4、5、...)
,內部的1、2、3、4、5、...
又滿足上面的分析,因此後續的操作重複上述步驟即可。
統計一下第二次中滿足條件的數字數量:n2=n/5/5
,101/25=(101/5)/5=4
。
因為25、50、75、100、125、...
它們都滿足相乘後產生至少兩個0,在第一次5*k
分析中已經統計過一次。對於n=101
,是20。因此此處的5*5*k
只要統計一次4即可,不需要根據25是5的二次冪統計兩次。
後面的125,250,...
等乘積為1000的可以為結果貢獻3個0的數字,只要在5*5*k
的基礎上再統計一次n3=((n/5)/5)/5
即可。
3、第三次
其實到這裡已經不用再寫,規律已經很清楚了。對於例子n=101
,只要根據規律進行101/125=((101/5)/5)/5=4/5=0
,退出統計。因此最終結果是20+4=24
。計算結束。
演算法3**
下面編寫打碼實現上面的思想。
public
class
solution
return count;}}
**分析:
演算法中每次迴圈均有除以5的操作,也就是每次都會將所要處理的資料量縮小至上一次的1/5
,容易推知時間複雜度為o(logn)。
至此,問題解決。
public
class
test
}
因為11不超過int型別的最大長度,所以並不會報錯。但是如果是5555550000000
,則會報錯:
the literal
5555550000000
oftype int is
outof
range
將數值進行強制型別轉換也不行:long innum=(long)5555550000000;
。
一種解決方法是使用scanner
直接讀取數值。
改進後的**如下:
public
class
test
}
這時輸入5555550000000
則不會報錯。
另外,如果需要的話,可使用system.currenttimemillis();
觀察**執行時間。
從最終的**來看,問題是挺簡單的。之所以折騰這麼久都沒有切入要害,直接做到真正的時間複雜度為o(logn)的效果,個人覺得是因為從分析題目的時候就沒有真正理解o(logn)的真正含義。
類似於二叉搜尋樹,從根節點開始比較,比根節點小則與左子樹比較,比根節點大則與右子樹比較,相等或到達葉子節點則退出。如此迴圈迭代。
每次判斷後,下一次可搜尋的資料量均為上一次的1/2
,如此迴圈複雜度為o(logn)。
LintCode 第二題 尾部的零
描述 設計乙個演算法,計算出n階乘中尾部零的個數 樣例 11 39916800,因此應該返回 2 假如你把1 2 4 n中每乙個因數分解質因數,結果就像 1 2 3 2 2 5 2 3 7 2 2 2 10進製數結尾的每乙個0都表示有乙個因數10存在 任何進製都一樣,對於乙個m進製的數,讓結尾多乙個...
Java實現n階階乘的計算
自然數由1 n的n個數連乘積叫作n的階乘,記作n 要求設計乙個演算法,可以根據輸入的數字計算其相應的階乘。這是乙個比較簡單的問題,所謂階乘實際就是n內數字的連續相乘的運算,想必大家都做過連加問題,倆者的解決思路其實很相近。但是要了解遞迴的思路實現會更加簡潔。既然階乘就是連續乘積,那麼我們可以設計乙個...
c 實現n階行列式計算
思路簡述 將n階行列式化為上三角行列式,對角元乘積之和即為行列式的值。include iostream using namespace std void main result 1 10階以上行列式要對a陣列修改大小 int i,j,k,t int size cout 請輸入行列式的階數 size ...