[LeetCode] 793. Preimage Size of Factorial Zeroes Function 階乘零的原像個數函數


 

Let f(x) be the number of zeroes at the end of x!. (Recall that x! = 1 * 2 * 3 * ... * x, and by convention, 0! = 1.)

For example, f(3) = 0 because 3! = 6 has no zeroes at the end, while f(11) = 2 because 11! = 39916800 has 2 zeroes at the end. Given K, find how many non-negative integers x have the property that f(x) = K.

Example 1:
Input: K = 0
Output: 5
Explanation: 0!, 1!, 2!, 3!, and 4! end with K = 0 zeroes.

Example 2:
Input: K = 5
Output: 0
Explanation: There is no x such that x! ends in K = 5 zeroes.

Note:

  • K will be an integer in the range [0, 10^9].

 

這道題的題目名稱非常的難懂,但是讀了題目內容以后,就不難理解了,定義函數 f(x) 為 x! 的末尾0的個數,現在給了我們一個非負整數K,問使得 f(x)=K 成立的非負整數的個數。之前做過一道有關階乘末尾零的個數的題 Factorial Trailing Zeroes,從那道里知道了末尾0其實是由2和5相乘為 10 得到的,而階乘中2的數量遠多於5的個數,所以 10 的個數就只取決於5的個數。需要注意的一點就是,像 25,125,這樣的不只含有一個5的數字需要考慮進去。比如,24 的階乘末尾有4個0,分別是 5,10,15,20 中的四個5組成的,而 25 的階乘末尾就有6個0,分別是 5,10,15,20 中的各一個5,還有 25 中的兩個5,所以共有六個5,那么就不存在其階乘數末尾有5個0的數。還有一個很重要的規律需要發現,對於 20,21,22,23,24,這五個數的階乘數末尾零的個數其實是相同的,都是有4個,因為它們包含的5的個數相同。而 19,18,17,16,15,這五個數末尾零個數相同,均為3。那么我們其實可以發現,每五個數,必會至少多出1個5,有可能更多。所以階乘末尾零個數均為K個的x值,只有兩種情況,要么是5,要么是0,這個規律得出來后,繼續向着正確的解題方向前進。

基於之前那道題 Factorial Trailing Zeroes 的解法,可以知道如何快速求一個給定的數字階乘末尾零的個數,那么只要找到了一個這樣的數,其階乘末尾零的個數等於K的話,那么就說明總共有5個這樣的數,返回5,反之,如果找不到這樣的數字,就返回0。像這種選一個 candidate 數字,再進行驗證的操作,用二分搜索法就是極好的,屬於博主的總結帖中 LeetCode Binary Search Summary 二分搜索法小結 的第四類,用子函數當作判斷關系。首先要確定二分搜索法的范圍,左邊界很好確定,為0就行了,關鍵是來確定右邊界,來分析一下,一個數字的階乘末尾零個數為K,那么這個數字能有多大,就拿前面舉的例子來說吧,末尾有4個0的最大數字是 24,有六個0的最大是 29,可以發現它們都不會超過 5*(K+1) 這個范圍,所以這就是右邊界,注意右邊界可能會超過整型數范圍,要用長整型來表示。那么之后就是經典的二分搜索法的步驟了,確定一個中間值 mid,然后調用子函數來計算 mid 的階乘數末尾零的個數,用來和K比較大小,如果相等了,直接返回5,如果小於K,那么更新 left 為 mid+1,反之,更新 right 為 mid 即可,最終沒找到的話,返回0即可,參見代碼如下:

 

解法一:

class Solution {
public:
    int preimageSizeFZF(int K) {
        long left = 0, right = 5L * (K + 1);
        while (left < right) {
            long mid = left + (right - left) / 2;
            long cnt = numOfTrailingZeros(mid);
            if (cnt == K) return 5;
            else if (cnt < K) left = mid + 1;
            else right = mid;
        }
        return 0;
    }
    long numOfTrailingZeros(long x) {
        long res = 0;
        for (; x > 0; x /= 5) {
            res += x / 5;
        }
        return res;
    }
};

 

下面這種解法是把子函數融到了 while 循環內,使得看起來更加簡潔一些,解題思路跟上面的解法一模一樣,參見代碼如下:

 

解法二:

class Solution {
public:
    int preimageSizeFZF(int K) {
        long left = 0, right = 5L * (K + 1);
        while (left < right) {
            long mid = left + (right - left) / 2, cnt = 0;
            for (long i = 5; mid / i > 0; i *= 5) {
                cnt += mid / i;
            }
            if (cnt == K) return 5;
            else if (cnt < K) left = mid + 1;
            else right = mid;
        }
        return 0;
    }
};

 

下面這種解法也挺巧妙的,也是根據觀察規律推出來的,我們首先來看x為1到 25 的情況:

 

x:    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
f(x): 0 0 0 0 1 1 1 1 1 2  2  2  2  2  3  3  3  3  3  4  4  4  4  4  6
g(x): 0 0 0 0 1 0 0 0 0 1  0  0  0  0  1  0  0  0  0  1  0  0  0  0  2

 

這里,f(x) 是表示 x! 末尾零的個數,而 g(x) = f(x) - f(x-1),其實還可以通過觀察發現,f(x) = sum(g(x)).

再仔細觀察上面的數字,發現 g(x) 有正值的時候都是當x是5的倍數的時候,那么來專門看一下x是5的倍數時的情況吧:

 

x:    5 10 15 20 25 30 35 40 45 50 55 60 65 70 75 80 85 90 95 100 105 110 115 120 125
g(x): 1 1  1  1  2  1  1  1  1  2  1  1  1  1  2  1  1  1  1   2   1   1   1   1   3

 

仔細觀察上面的紅色數字,g(x)=1 時,是5的倍數,g(x)=2 時,都是 25 的倍數,g(x)=3 時,是 125 的倍數,那么就有:

g(x) = 0     if x % 5 != 0,
g(x) >= 1    if x % 5 == 0,
g(x) >= 2   if x % 25 == 0.

如果繼續將上面的數字寫下去,就可以發現規律,g(x) 按照 1 1 1 1 x 的規律重復五次,第五次的時候x自增1。再繼續觀察:

當 x=25 時,g(x)=2,此時 K=5 被跳過了。

當 x=50 時,g(x)=2,此時 K=11 被跳過了。

當 x=75 時,g(x)=2,此時 K=17 被跳過了。

當 x=100 時,g(x)=2,此時 K=23 被跳過了。

當 x=125 時,g(x)=3,此時 K=29,30 被跳過了。

進一步,可以發現如下規律:

5(=1*5), 11(=6*1+5), 17(=6*2+5), 23(=6*3+5), 29(=6*4+5), 30(=6*5), 36(=31+5), 42(=31+6+5), 48(=31+6*2+5)

這些使得x不存在的K,出現都是有規律的,它們減去一個特定的基數 base 后,都是余5,而余 1,2,3,4 的,都是返回5。那么這個基 數base,實際是 1,6,31,156,...,是由 base = base * 5 + 1,不斷構成的,通過這種不斷對基數取余的操作,可以最終將K降為小於等於5的數,就可以直接返回結果了,參見代碼如下:

 

解法三:

class Solution {
public:
    int preimageSizeFZF(int K) {
        if (K < 5) return 5;
        int base = 1;
        while (base * 5 + 1 <= K) {
            base = base * 5 + 1;
        }
        if (K / base == 5) return 0;
        return preimageSizeFZF(K % base);
    }
};

 

Github 同步地址:

https://github.com/grandyang/leetcode/issues/793

 

類似題目:

Factorial Trailing Zeroes

 

參考資料:

https://leetcode.com/problems/preimage-size-of-factorial-zeroes-function/

https://leetcode.com/problems/preimage-size-of-factorial-zeroes-function/discuss/117649/4ms-Java-Math-Solution

https://leetcode.com/problems/preimage-size-of-factorial-zeroes-function/discuss/117631/C++-O(logn)-math-solution-with-explanation

https://leetcode.com/problems/preimage-size-of-factorial-zeroes-function/discuss/117821/Four-binary-search-solutions-based-on-different-ideas

 

LeetCode All in One 題目講解匯總(持續更新中...)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM