经常会用到组合数计算,利用数学公式想了一些巧妙的方法,但是如果不加数据结构或者算法的优化,很容易耗时过长或者越界。以下有三种方法。
1.直接利用公式comb(m,n)=comb(m-1,n)+comb(m-1,n-1)进行递归运算。
int comb(int m,int n)
{
if(m==n)return 1;
if(n==1)return m;
if(n==0)return 1;
if(n>m/2)n=m-n;
return comb(m-1,n)+comb(m-1,n-1);
}
考虑边界条件,与简单的n>m/2时的优化。这样算有很多冗余,会导致comb(m-1,n)后面重复调用计算。一种简单的方法是用数组A[m][n]存下计算过的comb(m-1,n-1);这个数据其实很不规整。部分用到容易出错,速度当然没问题。
2.利用指数运算
double lnchoose(int n, int m)
{
if (m > n)return 0;
if (m < n/2.0)m = n-m;
double s1 = 0;
for (int i=m+1; i<=n; i++)
s1 += log((double)i);
double s2 = 0;
int ub = n-m;
for (int i=2; i<=ub; i++)
s2 += log((double)i);
return exp(s1-s2);
}
没有任何递归或者数组缓存,用指数运算,先计算m+1到n的连乘,除以n-m的阶乘。exp转成线性。除了精度损失外还有在较小的数或者有,0,1的边界有bug。
3.利用公式与合适的数据结构进行动态规划
int fastcombine(int m,int n)
{
if(m==n)return 1;
if(n==0)return 1;
if(n>m/2)n=m-n;
m = m - n;//利用公式comb(m,n)=comb(m-1,n)+comb(m-1,n-1); 转变在(m-n)*n的矩阵中计算。两项分别对应左边和下边相邻的位置
vector<vector<int> > c(m+1,vector<int>(n+1,0));
c[0][0] = 1;
for(int i = 0; i <= m; i++)
c[i][0] = 1;
for(int j = 0; j <= n; j++)
c[0][j] = 1;
for(int i = 1; i <= m; i++)
for(int j = 1; j <= n; j++)
c[i][j] = c[i][j-1]+c[i-1][j];
return c[m][n];
}
利用公式comb(m,n)=comb(m-1,n)+comb(m-1,n-1); 转变到(m-n)*n的矩阵中计算。
c[i-1[j] | ||
c[i[j-2] | c[i[j-1] | c[i][j] |
comb(i+j-2,j-1) | comb(i+j-1,j) | |
comb(i+j-2,j-2) | comb(i+j-1,j-1) | comb(i+j,j) |
两项comb(m-1,n)+comb(m-1,n-1);分别对应左边和下边相邻的位置。这样用最少的规整的空间一次递推计算出组合数。时间O((m-n)*n).