一、常考点
含有相同元素的全排列:例如2个a,3个b,4个c可以组成多少个不同的字符串?9!/2!/3!/4!。
n个人的全排列:排成一排为n!,排成一圈且考虑旋转带来的差异也为n!,
排成一圈但不考虑旋转差异则为(n-1)!。
二、普通排列组合练习题
1、X*Y的方格阵中,从左上角走到右下角,每次只能走一格且只能向右走或向下走,有多少种走法
解法:向右一定走Y-1步,向下一定走X-1步,总共要走X+Y-2步,走法有C(X+Y-2,X-1)种。
当n<=10时:
组合数的计算方法一(利用递归公式):
class
Robot {
public
:
int
C(
int
n,
int
m) {
if
(m ==
1
)
return
n;
if
(m == n || m ==
0
)
return
1
;
return
C(n-
1
,m-
1
)+C(n-
1
,m);
}
int
countWays(
int
x,
int
y) {
return
C(x+y-
2
,x-
1
);
}
};
组合数的计算方法二(利用定义):
class
Robot {
public
:
int
countWays(
int
x,
int
y) {
int
n1 =
1
,n2 =
1
;
for
(
int
i =
1
,j = x+y-
2
;i <= x-
1
;i++,j--) {
n1 *= i;
n2 *= j;
}
return
n2/n1;
}
};
3、n个人站队,他们的编号依次从1到n,要求编号为a的人必须在编号为b的人的左边,但不要求一定相邻,请问共有多少种排法?第二问如果要求a必须在b的左边,并且一定要相邻,请问一共有多少种排法?
http://www.matrix67.com/blog/archives/393
解法:
class
StandInLine {
public
:
// 计算组合数
int
C(
int
n,
int
m) {
if
(m ==
1
)
return
n;
if
(m == n || m ==
0
)
return
1
;
return
C(n-
1
,m-
1
)+C(n-
1
,m);
}
// 计算阶乘
int
A(
int
n,
int
m) {
int
res =
1
;
for
(
int
i = n;i > n-m;i--) {
res *= i;
}
return
res;
}
vector<
int
> getWays(
int
n,
int
a,
int
b) {
vector<
int
> res;
res.push_back(A(n-
2
,n-
2
)*(C(n-
1
,
2
)+C(n-
1
,
1
)));
res.push_back(A(n-
1
,n-
1
));
return
res;
}
};
4、
n颗相同的糖果,分给m个人,每人至少一颗,问有多少种分法。
解法:○○|○○○○○|○○○这10颗糖中间有9个空,插两个隔板,分成3分,有C(9,2)种分法。
class
Distribution {
public
:
int
Comb(
int
n,
int
m) {
if
(n ==
0
&& m ==
0
)
return
1
;
if
(m ==
1
)
return
n;
if
(m == n || m ==
0
)
return
1
;
return
Comb(n-
1
,m-
1
)+Comb(n-
1
,m);
}
int
getWays(
int
n,
int
m) {
return
Comb(n-
1
,m-
1
);
}
};
三、复杂排列组合练习题
1、卡特兰数
当问题可以表示成以下两种形式时,就是卡特兰数问题。f(n)= C(2*n,n)/(n+1)。
①f(n) = f(0)*f(n-1)+
f(1)*f(n-2)+……+
f(n-2)*f(1)+
f(n-1)*f(0);
②f(n) = C(2*n,n)-C(2*n,n+1)。
2、
n对左右括号,请求出合法的排列有多少个
解法:将左括号记为1,右括号记为-1,则n对左右括号的序列就是1、-1序列,从左到右遍历该序列,并将遍历元素值叠加记为sum,假设遍历到第j个时sum=0说明在j的左边除了与j配对的括号对合法外,已遍历的j/2-1对括号也是合法的括号序列,剩下的(2n-j)/2对括号也是合法的括号序列,则这种情况的组合总数为f(j/2-1)*f(n-j/2),依次类推可知f(n) = f(0)*f(n-1)+ f(1)*f(n-2)+……+f(j/2-1)*f(n-j/2)+……+ f(n-2)*f(1)+ f(n-1)*f(0) = C(2*n,n)/(n+1)。
class
Parenthesis {
public
:
int
Comb(
int
n,
int
m) {
if
(n ==
0
)
return
0
;
if
(m ==
1
)
return
n;
if
(m == n || m ==
0
)
return
1
;
return
Comb(n-
1
,m-
1
)+Comb(n-
1
,m);
}
int
countLegalWays(
int
n) {
return
Comb(
2
*n,n)/(n+
1
);
}
};
3、n个数进出栈的顺序有多少种
解法:假设进栈前的序列中第i个数在出栈后的序列中也在第i个位置,则说明前i-1个数完成进栈出栈操作后第i个数才进栈出栈,也就是说问题可描述为f(n) = f(0)*f(n-1)+ f(1)*f(n-2)+……+ f(n-2)*f(1)+ f(n-1)*f(0)。
class
Stack {
public
:
int
countWays(
int
n) {
int
c1 =
1
,c2 =
1
;
for
(
int
i =
1
;i <= n;i++)
c1 *= i;
for
(
int
j = n+
1
;j <=
2
*n;j++)
c2 *= j;
return
c2/(c1*(n+
1
));
}
};
4、以下问题均是卡特兰数问题
①2n个人排队买票,n个人拿5块钱,n个人拿10块钱,票价是5块钱1张,每个人买一张票,售票员手里没有零钱,问有多少种排队方法让售票员可以顺利卖票。
②求n个无差别的节点构成的二叉树有多少种不同的结构?
③12个高矮不同的人,排成两排,每排必须是从矮到高排列,而且第二排比对应的第一排的人高,问排列方式有多少种?
5、
有n个信封,包含n封信,现在把信拿出来,再装回去,要求每封信不能装回它原来的信封,问有多少种装法?
解法:假设第i封信装入第1个信封,则剩下的信和信封有两种情况:①第1封信恰好也装入了第i个信封,问题为f(n-2);②第1封信没有装入第i个信封,问题为f(n-1)。而i有n-1种选择,所以原问题可以表示为f(n)=(n-1)(f(n-1)+f(n-2))。
class
CombineByMistake {
public
:
int
countWays(
int
n) {
if
(n ==
1
)
return
0
;
if
(n ==
2
)
return
1
;
long
f1 =
0
,f2 =
1
;
long
mod =
1000000007
;
for
(
int
i =
3
;i <= n;i++) {
long
temp = (i-
1
)*(f1+f2)%mod;
f1 = f2;
f2 = temp;
}
return
(
int
)f2;
}
};