組合數/二項式係數(Binomial Coefficient)的計算

問題描述

輸入: 兩個整數 n, m (n >= m >= 0);
輸出: 組合數 $\binom{n}{m}$.數組

解法一: 動態規劃(dp)

根據組合數公式 :
$$\binom{n}{m} = \binom{n-1}{m} + \binom{n-1}{m-1}$$
轉換爲二維遞推方程: dp[i][j] = dp[i-1][j] + dp[i-1][j-1]. 注意到當前的第i行的值只依賴於第i-1行,故能夠優化爲只用一個一維數組。優化

int compute_binomial(int n, int m) {
    vector<int> dp(m+1, 0);
    dp[0] = 1;
    for (int i = 1; i <= n; ++i)
        for (int j = m; j > 0; ++j)
            dp[j] = dp[j] + dp[j-1];
    return dp[m];        
}
  • 時間複雜度:O(m*n)
  • 空間複雜度:O(n)

Note: 該方法其實計算出了一串組合數序列 $\binom{n}{1}, ..., \binom{n}{m}$, 所以能夠直接用於計算二項式展開的全部係數。code

解法二: 根據定義直接計算

由組合數的定義:
$$\binom{n}{m} = \frac{n!}{m!(n-m)!} = \frac{n \times (n-1) \times \dots \times (n-m+1)}{1 \times 2 \times \dots \times m}$$
顯然簡化後的公式比直接計算三個階乘開銷更小,然而須要注意的是乘法溢出的問題。咱們但願儘可能在計算過程當中就約分,而不是分別計算分子分母的乘積。循環

int compute_binomial(int n, int m) {
    long long res = 1;
    for (int i = 1; i <= m; ++i) {
        res *= n - i + 1;
        // 這裏的除法是精確的 assert(res % i == 0);
        res /= i;
    }
    return (int)res;
}

以上的乘法仍有可能致使溢出,即res的範圍可能比組合數的大,即便最後結果屬於int範圍,res的類型也須要long long。但除法必定是能除盡的,爲何呢?由於在第i次循環中,根據定義式咱們其實是在計算$\binom{n}{i}$,這也是爲何乘法是從n開始乘,而不是從n-m+1開始(若是那樣則不能保證都能整除)。方法

  • 時間複雜度:O(m)
  • 空間複雜度:O(1)
相關文章
相關標籤/搜索