📜  nCr (1)

📅  最后修改于: 2023-12-03 14:44:33.501000             🧑  作者: Mango

nCr

在组合数学中,组合数是从n个不同元素中取r个元素的排列数,通常表示为nCr 或C(n, r)。具体求法是:

  • 排列:从n个不同元素中取r个元素,有n!种取法,但取出来的r个元素的顺序不同,因此要除以r!得到排列数,即A(n, r) = n! / (n-r)!
  • 组合:从n个不同元素中取r个元素,有nCr种取法,因为取出来的r个元素的顺序不同,但对于组合数,我们认为这些不同的排列是等效的,因此要除以r!得到组合数,即C(n, r) = A(n, r) / r! = n! / (r! (n-r)! )

组合数在计算机科学中非常常见,特别是在算法竞赛中。在实现算法的时候,计算组合数可能会被频繁使用到,因此了解组合数的计算方法和效率优化可以帮助程序员更好地实现算法。

常规算法

计算组合数有多种方法,最基础的方法是直接套用公式计算。这种方法的时间复杂度为O(r),空间复杂度为O(1)。

int nCr(int n, int r) {
    int res = 1;
    for (int i = 0; i < r; i++) {
        res *= n - i;
        res /= i + 1;
    }
    return res;
}
递归算法

另一种计算组合数的方法是使用递归算法,由于递归需要反复调用自身,因此这种方法的时间复杂度很高,空间复杂度也很高,不过在一些较小数据量的情况下仍然可以使用。

int nCr(int n, int r) {
    if (r == 0 || r == n) return 1;
    else return nCr(n - 1, r - 1) + nCr(n - 1, r);
}
杨辉三角

杨辉三角是一种特殊的数列,它可以用来快速计算组合数。杨辉三角的每一行都是一个组合数系数,我们可以使用递推公式计算下一行:

C(n, 0) = 1
C(n, n) = 1
C(n, k) = C(n-1, k-1) + C(n-1, k)

代码实现:

vector<vector<int>> generate(int numRows) {
    vector<vector<int>> res(numRows);
    for (int i = 0; i < numRows; i++) {
        res[i].resize(i + 1);
        res[i][0] = res[i][i] = 1;
        for (int j = 1; j < i; j++) {
            res[i][j] = res[i - 1][j - 1] + res[i - 1][j];
        }
    }
    return res;
}
效率优化

在实现算法的时候,需要重复计算的组合数较多,因此我们可以将已经计算的组合数缓存起来,以便后续的计算中直接使用。这种方法被称为记忆化搜索,实现起来很简单,只需要使用一个数组缓存结果即可。

另外,对于大量计算的问题,往往需要用到取模操作,因为这可以防止结果溢出。对于组合数计算而言,可以使用费马小定理来加速模运算。

int nCr(int n, int r, int mod) {
    if (r > n) return 0;
    if (r == 0 || r == n) return 1;
    if (r == 1 || r == n - 1) return n;
    int res = 1;
    for (int i = 0; i < r; i++) {
        res = 1LL * res * (n - i) % mod;
        res = 1LL * res * powmod(i + 1, mod - 2, mod) % mod;
    }
    return res;
}

其中,powmod函数是计算a的b次方对于mod取模的结果,实现代码可以使用快速幂算法实现。

总结

通过本文的介绍,我们了解到组合数的定义和计算方法,知道了常规算法、递归算法和杨辉三角等计算组合数的方法,并学习了记忆化搜索和费马小定理优化算法的方法,这些都是计算机科学中非常常见的算法,希望本文能够帮助程序员更好地理解组合数的计算方法和应用。