1. 程式人生 > 實用技巧 >每日一道 LeetCode (41):階乘後的零

每日一道 LeetCode (41):階乘後的零

每天 3 分鐘,走上演算法的逆襲之路。

前文合集

每日一道 LeetCode 前文合集

程式碼倉庫

GitHub: https://github.com/meteor1993/LeetCode

Gitee: https://gitee.com/inwsy/LeetCode

題目:階乘後的零

給定一個整數 n,返回 n! 結果尾數中零的數量。

示例 1:

輸入: 3
輸出: 0
解釋: 3! = 6, 尾數中沒有零。

示例 2:

輸入: 5
輸出: 1
解釋: 5! = 120, 尾數中有 1 個零.

說明: 你演算法的時間複雜度應為 O(log n) 。

解題方案一:暴力計算

看到這道題,常人的思維最少應該有先把 n! 算出來,再通過 /10 來計算末尾有多少個 0 。

// 暴力硬算
public int trailingZeroes(int n) {
    // 先定義第一個數字
    BigInteger bi = BigInteger.ONE;
    for (int i = 2; i <= n; i++) {
        bi = bi.multiply(BigInteger.valueOf(i));
    }

    // 定義 0 出現的次數
    int count = 0;
    while (bi.mod(BigInteger.TEN).equals(BigInteger.ZERO)) {
        bi = bi.divide(BigInteger.TEN);
        count++;
    }
    return count;
}

我累個擦,直接超時了,然後看下測試用例,竟然執行到一個 4327 的數字,拿 4 位數出來算階乘,就是計算機來算我都覺得累得慌,這個測試用例我服了,甘拜下風。

麼得辦法了,看答案吧,我這個智商也就只能想到這種方案了。

解題方案二:計算因子 5

先想一下,啥情況下能產生 0 ,最小的產生因子是 2 * 5 。

借用一個答案上的示例,42 * 75 = 3150 ,這個算式可以拆解如下:

42 = 2 * 3 * 7
75 = 3 * 5 * 5
42 * 75 = 2 * 3 * 7 * 3 * 5 * 5

這裡面只出現了一對 2 和 5 ,所以結果只有一個 0 。

接著看另一個案例,比如求 19! ,這裡麵包含 5 的因子有 5 、10 、15 ,包含 2 的因子有2、4、6、8、10、12、14、16、18 。

可以看到的規律是每 5 個數字就會有一個包含 5 的因子,而在這 5 個數中間,肯定至少有一個包含 2 的因子,實際上是有 2 個,所以就不需要考慮 2 這個因子了,單純的考慮 5 就好了,那麼演算法就演變成了我們需要尋找包含 5 的因子,程式碼如下:

// 計算因子 5
public int trailingZeroes_1(int n) {
    int count = 0;
    for (int i = 5; i <= n; i += 5) {
        int current = i;
        while (current % 5 == 0) {
            count++;
            current /= 5;
        }
    }
    return count;
}

結果又超時了,我心態崩了啊,答案給的都是超時,看這個輸入的數字, TM 18 億,還敢輸入數字再大點嘛。

接著剛才的答案往下看。

答案上還給出了另一種方案,不需要每次檢查是否可以被 5 整除,還可以直接檢查是否可以被 5 的次冪整除:

public int trailingZeroes_2(int n) {
    int count = 0;
    for (int i = 5; i <= n; i += 5) {
        int powerOf5 = 5;
        while (i % powerOf5 == 0) {
            count += 1;
            powerOf5 *= 5;
        }
    }
    return count;
}

這個方案實際上和上面的方案是一樣的,沒什麼本質的區別。

解題方案三:高效的計算因子 5

接著往下看,更加高效的計算方案。

看下面這個階乘方案:

n! = 1 * 2 * 3 * 4 * (1 * 5) * ... * (2 * 5) * ... * (3 * 5) *... * n

我們的目標是計算出現了多少個 5 ,從上面這個公式看下來,每隔 5 個數就會出現一次,那麼我們使用 n / 5 就可以計算出來。

但是還沒有結束,接著看下面的公式變化:

... * (1 * 5) * ... * (1 * 5 * 5) * ... * (2 * 5 * 5) * ... * (3 * 5 * 5) * ... * n

從這個公式可以看出來,每隔 5 個數,出現一個 5 ,每隔 25 個數,出現 2 個 5 ,每隔 125 個數,出現 3 個 5 ,以此類推。。。

最終出現 5 的個數就是 n / 5 + n / 25 + n / 125 ...

題解到這一步就能開始寫程式了:

// 更加高效的計算因子 5
public int trailingZeroes_3(int n) {
    int count = 0;
    while (n > 0) {
        n /= 5;
        count += n;
    }
    return count;
}

終於出結果了,我太難了,不過 LeetCode 把這道題的難度定成簡單真的合適麼。。。