講兩道常考的階乘演算法題

2022-07-04 01:30:19 字數 4954 閱讀 1204

172.階乘後的零

793.階乘後k個零

-----------

1、輸入乙個非負整數n,請你計算階乘n!的結果末尾有幾個 0

比如說輸入n = 5,演算法返回 1,因為5! = 120,末尾有乙個 0。

函式簽名如下:

int trailingzeroes(int n);
2、輸入乙個非負整數k,請你計算有多少個n,滿足n!的結果末尾恰好有k個 0

比如說輸入k = 1,演算法返回 5,因為5!,6!,7!,8!,9!這 5 個階乘的結果最後只有乙個 0,即有 5 個n滿足條件。

函式簽名如下:

int preimagesizefzf(int k);
我把這兩個題放在一起,肯定是因為它們有共性,下面我們來逐一分析。

肯定不可能真去把n!的結果算出來,階乘增長可是比指數增長都恐怖,趁早死了這條心吧。

那麼,結果的末尾的 0 從**來的?我們有沒有投機取巧的方法計算出來?

首先,兩個數相乘結果末尾有 0,一定是因為兩個數中有因子 2 和 5,因為 10 = 2 x 5。

也就是說,問題轉化為:n!最多可以分解出多少個因子 2 和 5

比如說n = 25,那麼25!最多可以分解出幾個 2 和 5 相乘?這個主要取決於能分解出幾個因子 5,因為每個偶數都能分解出因子 2,因子 2 肯定比因子 5 多得多。

25!中 5 可以提供乙個,10 可以提供乙個,15 可以提供乙個,20 可以提供乙個,25 可以提供兩個,總共有 6 個因子 5,所以25!的結果末尾就有 6 個 0。

ps:我認真寫了 100 多篇原創,手把手刷 200 道力扣題目,全部發布在 labuladong的演算法小抄,持續更新。建議收藏,按照我的文章順序刷題,掌握各種演算法套路後投再入題海就如魚得水了。

現在,問題轉化為:n!最多可以分解出多少個因子 5

難點在於像 25,50,125 這樣的數,可以提供不止乙個因子 5,怎麼才能不漏掉呢?

這樣,我們假設n = 125,來算一算125!的結果末尾有幾個 0:

首先,125 / 5 = 25,這一步就是計算有多少個像 5,15,20,25 這些 5 的倍數,它們一定可以提供乙個因子 5。

但是,這些足夠嗎?剛才說了,像 25,50,75 這些 25 的倍數,可以提供兩個因子 5,那麼我們再計算出125!中有 125 / 25 = 5 個 25 的倍數,它們每人可以額外再提供乙個因子 5。

夠了嗎?我們發現 125 = 5 x 5 x 5,像 125,250 這些 125 的倍數,可以提供 3 個因子 5,那麼我們還得再計算出125!中有 125 / 125 = 1 個 125 的倍數,它還可以額外再提供乙個因子 5。

這下應該夠了,125!最多可以分解出 20 + 5 + 1 = 31 個因子 5,也就是說階乘結果的末尾有 31 個 0。

理解了這個思路,就可以理解解法**了:

int trailingzeroes(int n) 

return res;

}

這裡divisor變數使用 long 型,因為假如n比較大,考慮 while 迴圈的結束條件,divisor可能出現整型溢位。

上述**可以改寫地更簡單一些:

int trailingzeroes(int n) 

return res;

}

這樣,這道題就解決了,時間複雜度是底數為 5 的對數,也就是o(logn),我們看看下如何基於這道題的解法完成下一道題目。

ps:我認真寫了 100 多篇原創,手把手刷 200 道力扣題目,全部發布在 labuladong的演算法小抄,持續更新。建議收藏,按照我的文章順序刷題,掌握各種演算法套路後投再入題海就如魚得水了。

現在是給你乙個非負整數k,問你有多少個n,使得n!結果末尾有k個 0。

乙個直觀地暴力解法就是窮舉唄,因為隨著n的增加,n!肯定是遞增的,trailingzeroes(n!)肯定也是遞增的,偽碼邏輯如下:

int res = 0;

for (int n = 0; n < +inf; n++)

if (trailingzeroes(n) > k)

if (trailingzeroes(n) == k)

}return res;

前文 二分查詢如何運用 說過,對於這種具有單調性的函式,用 for 迴圈遍歷,可以用二分查詢進行降維打擊,對吧?

搜尋有多少個n滿足trailingzeroes(n) == k,其實就是在問,滿足條件的n最小是多少,最大是多少,最大值和最小值一減,就可以算出來有多少個n滿足條件了,對吧?那不就是二分查詢「搜尋左側邊界」和「搜尋右側邊界」這兩個事兒嘛?

先不急寫**,因為二分查詢需要給乙個搜尋區間,也就是上界和下界,上述偽碼中n的下界顯然是 0,但上界是+inf,這個正無窮應該如何表示出來呢?

首先,數學上的正無窮肯定是無法程式設計表示出來的,我們一般的方法是用乙個非常大的值,大到這個值一定不會被取到。比如說 int 型別的最大值int_max(2^31 - 1,大約 31 億),還不夠的話就 long 型別的最大值long_max(2^63 - 1,這個值就大到離譜了)。

那麼我怎麼知道需要多大才能「一定不會被取到」呢?這就需要認真讀題,看看題目給的資料範圍有多大

這道題目實際上給了限制,k是在[0, 10^9]區間內的整數,也就是說,trailingzeroes(n)的結果最多可以達到10^9

然後我們可以反推,當trailingzeroes(n)結果為10^9時,n為多少?這個不需要你精確計算出來,你只要找到乙個數hi,使得trailingzeroes(hi)10^9大,就可以把hi當做正無窮,作為搜尋區間的上界。

剛才說了,trailingzeroes函式是單調函式,那我們就可以猜,先算一下trailingzeroes(int_max)的結果,比10^9小一些,那再用long_max算一下,遠超10^9了,所以long_max可以作為搜尋的上界。

注意為了避免整型溢位的問題,trailingzeroes函式需要把所有資料型別改成 long

// 邏輯不變,資料型別全部改成 long

long trailingzeroes(long n)

return res;

}

現在就明確了問題:

在區間[0, long_max]中尋找滿足trailingzeroes(n) == k的左側邊界和右側邊界

根據前文 二分查詢演算法框架,可以直接把搜尋左側邊界和右側邊界的框架 copy 過來:

/* 主函式 */

int preimagesizefzf(int k)

/* 搜尋 trailingzeroes(n) == k 的左側邊界 */

long left_bound(int target) else if (trailingzeroes(mid) > target) else

}return lo;

}/* 搜尋 trailingzeroes(n) == k 的右側邊界 */

long right_bound(int target) else if (trailingzeroes(mid) > target) else

}return lo - 1;

}

如果對二分查詢的框架有任何疑問,建議好好複習一下前文 二分查詢演算法框架,這裡就不展開了。

現在,這道題基本上就解決了,我們來分析一下它的時間複雜度吧。

時間複雜度主要是二分搜尋,從數值上來說long_max是 2^63 - 1,大得離譜,但是二分搜尋是對數級的複雜度,log(long_max) 是乙個常數;每次二分的時候都會呼叫一次trailingzeroes函式,複雜度 o(logk);所以總體的時間複雜度就是 o(logk)。

_____________

C 面試常考的兩道題

1.說明一下const和readonly的區別 區別有兩點 1.const是乙個確定的值,不可以修改,適用於自然常量,如pi,如 系統引數配置 readonly可以在執行時修改,比如在建構函式裡可以修改。2.const修飾的是變數,readonly修飾的是字段。2.說說using的用法 1.最常見到...

兩道面試演算法題

最近面試 兩道演算法題 說難不難 要寫全對也不容易 很慚愧 我沒有一次寫對 第一道 無序int陣列 找到中位數 void swap int a,int b int get kth number vector num,int k,int start,int end 一次劃分結束 index i if ...

兩道貪心演算法題

假設有n項物品,大小分別為s 1 s 2 s i sn 其中s i為滿足1 s i 100的整數。要把這些物品裝入到容量為100的一批箱子 序號1 n 中。裝箱方法是 對每項物品,順序掃瞄箱子,把該物品放入足以能夠容下它的第乙個箱子中。請寫乙個程式模擬這種裝箱過程,並輸出每個物品所在的箱子序號,以及...