lintcode第二題 計算n階乘中尾部零的個數

2021-08-13 10:50:31 字數 4107 閱讀 8155

設計乙個演算法,計算出n階乘中尾部零的個數

樣例11! = 39916800,因此應該返回 2

挑戰o(logn)的時間複雜度

先說結論,此問題大致有三種思路:第一種算出結果,然後檢視末尾的0的個數,效果非常差;第二種,加法操作,從5開始,每次進5,然後判斷,效果達不到o(logn);第三種,每次除5,多次之後結束。 

詳情如下。

重點分析在演算法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,100101/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/5101/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 ...