【数据结构与算法/组合数学】出栈序列的个数问题详解
假设有一个入栈序列a1,a2,⋯ ,ana_1,a_2,\cdots,a_na1,a2,⋯,an,现在求它有多少种出栈序列。例如,对于入栈序列1,2,31,2,31,2,3,我们有555种出栈序列:①3,2,1①3,2,1①3,2,1:push 1,push 2,push 3,pop 3,pop 2,pop 1②2,3,1②2,3,1②2,3,1:push 1,push 2,pop 2,pu
假设有一个入栈序列 a 1 , a 2 , ⋯ , a n a_1,a_2,\cdots,a_n a1,a2,⋯,an,现在求它有多少种出栈序列。
例如,对于入栈序列
1
,
2
,
3
1,2,3
1,2,3,我们有
5
5
5种出栈序列:
①
3
,
2
,
1
①3,2,1
①3,2,1:push 1
,push 2
,push 3
,pop 3
,pop 2
,pop 1
②
2
,
3
,
1
②2,3,1
②2,3,1:push 1
,push 2
,pop 2
,push 3
,pop 3
,pop 1
③
1
,
3
,
2
③1,3,2
③1,3,2:push 1
,pop 1
,push 2
,push 3
,pop 3
,pop 2
④
1
,
2
,
3
④1,2,3
④1,2,3:push 1
,pop 1
,push 2
,pop 2
,push 3
,pop 3
⑤
2
,
1
,
3
⑤2,1,3
⑤2,1,3:push 1
,push 2
,pop 2
,pop 1
,push 3
,pop 3
现在我们考虑对任意 n n n的情况。令 f ( i ) f(i) f(i)代表长度为 i i i的序列的出栈序列个数。则我们要求的就是 f ( n ) f(n) f(n)。
显然,
f
(
0
)
=
f
(
1
)
=
1
f(0)=f(1)=1
f(0)=f(1)=1,因为没有元素和只有一个元素的时候都只有一种顺序。有
n
n
n个元素时,问题太大,我们采用分而治之的思想,递归求解。考虑入栈的第一个元素
a
1
a_1
a1在出栈序列中的位置。设
a
1
a_1
a1是第
j
j
j个出栈的,则
a
2
∼
a
j
a_2\sim a_j
a2∼aj必须在
a
1
a_1
a1之前出栈。这个性质是十分关键的。要理解这个性质,必须运用栈的LIFO (Last In First Out)
(后进先出)特征。因为
a
2
∼
a
j
a_2\sim a_j
a2∼aj比
a
1
a_1
a1后入栈,那就比
a
1
a_1
a1先出栈。换句话说,
a
2
∼
a
j
a_2\sim a_j
a2∼aj一定是压在
a
1
a_1
a1头顶的,如果它们不出去,
a
1
a_1
a1就没法出去。当
a
1
a_1
a1出去时,栈一定是空的。那么下面就简单了。现在
a
1
a_1
a1把出栈序列分割为两部分:
a
2
∼
a
j
a_2\sim a_j
a2∼aj,
a
j
+
1
∼
a
n
a_{j+1}\sim a_n
aj+1∼an。我们看到,前一部分和后一部分是相互独立的。而前一部分的出栈顺序个数为
f
(
j
−
1
)
f(j-1)
f(j−1),后面一部分的出栈顺序个数为
f
(
n
−
j
)
f(n-j)
f(n−j)。因为二者互不影响,所以贡献的总出栈顺序个数为
f
(
j
−
1
)
×
f
(
n
−
j
)
f(j-1)\times f(n-j)
f(j−1)×f(n−j)。如果你不理解,可以先考虑前一部分有一个固定的顺序,那么后一部分有
f
(
n
−
j
)
f(n-j)
f(n−j)种顺序;再令前一部分的顺序变化,共有
f
(
j
−
1
)
f(j-1)
f(j−1)种变化,所以结果就是
f
(
j
−
1
)
f(j-1)
f(j−1)个
f
(
n
−
j
)
f(n-j)
f(n−j),即
f
(
j
−
1
)
×
f
(
n
−
j
)
f(j-1)\times f(n-j)
f(j−1)×f(n−j)。好了,这就是对于一个固定的
j
j
j的顺序个数。再令
j
j
j取到
1
∼
n
1\sim n
1∼n,用加法原理把所有可能性加起来,就是
f
(
n
)
=
f
(
0
)
f
(
n
−
1
)
+
f
(
1
)
f
(
n
−
2
)
+
⋯
+
f
(
n
−
1
)
f
(
0
)
=
∑
j
=
1
n
f
(
j
−
1
)
f
(
n
−
j
)
\begin{aligned}f(n)&=f(0)f(n-1)+f(1)f(n-2)+\cdots+f(n-1)f(0)\\&=\sum\limits_{j=1}^nf(j-1)f(n-j)\end{aligned}
f(n)=f(0)f(n−1)+f(1)f(n−2)+⋯+f(n−1)f(0)=j=1∑nf(j−1)f(n−j)这里要求
n
≥
2
n\ge2
n≥2。这个递推公式解起来挺麻烦的,我们直接给出结论。事实上,
f
(
n
)
f(n)
f(n)就是卡特兰数(Catalan Numbers),记作
C
n
C_n
Cn(不要与组合数混淆QwQ),它的的公式是
C
n
=
(
2
n
)
!
(
n
+
1
)
!
n
!
=
1
n
+
1
C
2
n
n
C_n=\frac{(2n)!}{(n+1)!n!}=\frac{1}{n+1}C_{2n}^{n}
Cn=(n+1)!n!(2n)!=n+11C2nn可以验证它满足我们推出的递推公式。当
n
=
3
n=3
n=3时,
C
3
=
6
!
4
!
3
!
=
720
24
×
6
=
120
24
=
5
C_3=\frac{6!}{4!3!}=\frac{720}{24\times6}=\frac{120}{24}=5
C3=4!3!6!=24×6720=24120=5,与我们在开头提到的
1
,
2
,
3
1,2,3
1,2,3有
5
5
5种出栈序列相符。
出栈序列的个数问题与很多问题等价。例如,求 n n n个左括号和 n n n个右括号能组成多少合法的括号序列。这里的合法指的是每个括号都有配对。 n = 3 n=3 n=3时有 5 5 5种: ( ( ( ) ) ) , ( ( ) ( ) ) , ( ( ) ) ( ) , ( ) ( ) ( ) , ( ) ( ( ) ) ((())),\ (()()),\ (())(),\ ()()(),\ ()(()) ((())), (()()), (())(), ()()(), ()(()),恰与 3 3 3个元素的出栈序列个数相同。这种括号序列问题的答案也是卡特兰数。
最后附上求任一入栈序列的所有出栈序列的代码:
#include <iostream>
#include <string>
#include <vector>
using namespace std;
int n, ans;
string s, a;
vector<char> stk;
void dfs(int i)
{
// --- pop
if(!stk.empty())
{
a.push_back(stk.back());
if(a.size() == n)
cout << a << '\n', ++ans;
stk.pop_back();
dfs(i);
stk.push_back(a.back());
a.pop_back();
}
// --- push
if(i < n)
{
stk.push_back(s[i]);
dfs(i + 1);
stk.pop_back();
}
}
int main()
{
cout << "Please enter the in-stack "
"sequence:\n>>> ";
// e.g. ABCED
cin >> s;
n = s.size();
dfs(0);
cout << "Totally " << ans << " sequences.\n";
return 0;
}
例如
Please enter the in-stack sequence:
>>> ABCD
ABCD
ABDC
ACBD
ACDB
ADCB
BACD
BADC
BCAD
BCDA
BDCA
CBAD
CBDA
CDBA
DCBA
Totally 14 sequences.
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)