题目地址:
https://leetcode.com/problems/generate-parentheses/
生成所有 2 n 2n 2n长度的合法的括号序列。
直接DFS,枚举的顺序是,先枚举左括号,再枚举右括号。需要注意何时可以枚举左括号,何时可以枚举右括号。为了提高效率,在深搜的时候,我们直接将当前已经获得的括号序列中左右括号的个数当成参数传递给下一层(否则的话不利于剪枝。因为如果到某一层枚举的时候,如果左括号个数小于右括号个数,或者左括号个数大于了 n n n,就已经得到了一个非法的括号序列了,要及时剪枝。如果将左右括号个数传递下去,就不用数已经获得的序列里有多少左右括号了)。代码如下:
class Solution {
public:
vector<string> generateParenthesis(int n) {
vector<string> res;
string s;
dfs(s, 0, 0, res, n);
return res;
}
void dfs(string& s, int l, int r, vector<string>& res, int n) {
if (r == n) {
res.push_back(s);
return;
}
if (l < n) {
s += '(';
dfs(s, l + 1, r, res, n);
s.pop_back();
}
if (l > r) {
s += ')';
dfs(s, l, r + 1, res, n);
s.pop_back();
}
}
};
时间复杂度 O ( 4 n n ) O(\frac{4^n}{\sqrt{n}}) O(n4n),空间复杂度 O ( n ) O(n) O(n)。
时间复杂度和Catalan数有关。合法的括号序列的数量等于Catalan数 C n = 1 n + 1 ( 2 n n ) ∼ 4 n n 3 2 π C_n=\frac{1}{n+1} {2n \choose n}\sim \frac{4^n}{n^{\frac{3}{2}}\sqrt{\pi}} Cn=n+11(n2n)∼n23π4n所以时间复杂度就是 O ( n 4 n n 3 2 π ) = O ( 4 n n ) O(n\frac{4^n}{n^{\frac{3}{2}}\sqrt{\pi}})=O(\frac{4^n}{\sqrt{n}}) O(nn23π4n)=O(n4n),即生成括号序列,并加入res,每次加入需要 O ( 2 n ) O(2n) O(2n)的时间。
算法正确性证明:
只需证明所有的合法括号序列都被生成了,并且生成的序列都合法即可。
首先证明一个长度为 2 n 2n 2n的括号序列合法,当且仅当对于这个序列的任意前缀,都有左括号数量大于等于右括号数量,并且整个序列左括号数量等于 n n n(于是右括号数量也等于 n n n)。先记几个符号,设 l s l_s ls和 r s r_s rs代表这个序列长度为 s s s的前缀中左右括号各自的个数。
证明如下:
先证必要性:如果一个长度为
2
n
2n
2n的括号序列合法,那么如果
∃
s
\exist s
∃s使得
l
s
<
r
s
l_s<r_s
ls<rs,那么前
s
s
s个字符中必然有一个右括号没有左括号与之匹配,矛盾。至于
l
2
n
=
n
l_{2n}=n
l2n=n是显然的。
再证充分性:只需要模拟栈的操作即可。因为
r
2
n
=
n
r_{2n}=n
r2n=n,所以必然存在
i
i
i使得
r
i
=
1
r_i=1
ri=1,那么由于
l
i
≥
r
i
l_i\ge r_i
li≥ri,我们就可以将与第一个右括号匹配的左括号删去,然后再找
i
i
i使得
r
i
=
2
r_i=2
ri=2,由于
l
i
−
1
≥
r
i
−
1
l_i-1\ge r_i-1
li−1≥ri−1,所以就可以将与第二个右括号匹配的左括号删去,如此操作下去,由于
l
2
n
≥
r
2
n
l_{2n}\ge r_{2n}
l2n≥r2n,所以到序列末尾的时候,所有右括号都被删除干净了。又由于
l
2
n
=
n
l_{2n}=n
l2n=n,所以
r
2
n
=
n
r_{2n}=n
r2n=n,所以左括号此时也被删除干净了,所以序列合法,证毕。
接下来证明算法正确性。
先证所有得到的序列都合法。数学归纳法。一开始 l 0 = r 0 = 0 l_0=r_0=0 l0=r0=0,满足 l 0 ≥ r 0 l_0\ge r_0 l0≥r0。设递归树的第 k k k层也满足,接下来如果走第一个分叉,那么显然仍然满足,如果走第二个分叉,只有在 l k > r k l_k>r_k lk>rk的时候才会走第二个分叉,显然也满足。由数学归纳法,任意时刻都满足 l j ≥ r j l_j\ge r_j lj≥rj。由于 l j ≤ n l_j\le n lj≤n,所以如果某个序列被加进了res,必然有 l 2 n = r 2 n = n l_{2n}=r_{2n}=n l2n=r2n=n,所以序列合法。
再证所有合法的序列都成功生成了。因为对于每个合法的序列都有前缀 l s ≥ r s l_s\ge r_s ls≥rs,并且 l s ≤ n l_s\le n ls≤n,所以对于某个合法序列,只需要按照其从左到右的顺序依次走二叉树的某个叉即可证明该序列可以生成。证毕。