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 中。裝箱方法是 對每項物品,順序掃瞄箱子,把該物品放入足以能夠容下它的第乙個箱子中。請寫乙個程式模擬這種裝箱過程,並輸出每個物品所在的箱子序號,以及...