22-括号生成
@Author:CSU张扬
@Email:csuzhangyang@gmail.com or csuzhangyang@qq.com
Category | Difficulty | Pass rate | Tags | Companies |
---|---|---|---|---|
algorithms | Medium | 72.20% | string / backtracking | google / uber / zenefits |
1. 题目
给出 代表生成括号的对数,请你写出一个函数,使其能够生成所有可能的并且有效的括号组合。
例如,给出 n = 3,生成结果为:
[
"((()))",
"(()())",
"(())()",
"()(())",
"()()()"
]
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/generate-parentheses/
2. 解法
2.1 解法一:回溯
以 n = 3
为例:我们的字符串 s
一共有 6
个位置来放置 '('
和 ')'
。
- 首先我们知道:第一个位置一定是
'('
,此时s = ( _ _ _ _ _
。 - 此时我们考虑第二个位置,我们选择可以放置
'('
或')'
。
那么什么情况下,我们可以有两个选择呢?
很容易想到,应该是当'('
的数量大于')'
的数量 并且'('
的数量小于n
时,即if (left < N && left > right)
。
可以思考s = ( ( ) _ _ _
和s = ( ( ( ) _ _
这两种情况下,下一个位置应该放哪种括号。 - 步骤2中,我们可以有两个选择。那么我们接下来就应该考虑什么情况下只有一种选择。
- 只能放置
'('
。
易知,当'('
的数量等于')'
的数量时,我们只能向后添加'('
。
可以思考s = ( ( ) ) _ _
和s = ( ) ( ) _ _
这两种情况下,下一个位置应该放哪种括号。 - 只能放置
')'
。
易知,当'('
的数量等于n
时,我们只能向后添加')'
。
可以思考s = ( ( ( _ _ _
和s = ( ) ( ( _ _
这两种情况下,下一个位置应该放哪种括号。
我们的回溯函数 void backtrack(int left, int right, const string &s)
中,left
和 right
代表的是 s
中左右括号的数量。
当 left + right == 2 * N
时,回溯结束。
注意:
初始情况下,我们必须要先添加一个 '('
,此时 s = ( _ _ _ _ _
,所以参数 left = 1, right = 0
。
执行用时: 4 ms, 在所有 cpp 提交中击败了96.60%的用户
内存消耗: 17.4 MB, 在所有 cpp 提交中击败了36.95%的用户
class Solution {
public:
vector<string> res;
int N;
vector<string> generateParenthesis(int n) {
N = n;
string s = "";
backtrack(1, 0, s + "(");
return res;
}
void backtrack(int left, int right, const string &s) {
if (left + right == 2 * N) {
res.push_back(s);
return;
}
if (left < N && left > right) {
backtrack(left + 1, right, s + "(");
backtrack(left, right + 1, s + ")");
} else if (left == N) {
backtrack(left, right + 1, s + ")");
} else if (left == right) {
backtrack(left + 1, right, s + "(");
}
}
};
2.2 解法二:动态规划
首先看 n
较小时的结果。
n = 0
是,res = { "" }
,为空。n = 1
是,res = { "()" }
。n = 2
是,res = { "(())", "()()" }
。n = 3
是,res = { "((()))","(()())","(())()","()(())","()()()" }
。
问题来了,你没有考虑过每一个 n
的结果,都和比它小的 n
的结果,有点关联?
我们可以这么看待这个问题:
n = i
的结果其实就是比n = i - 1
多了一对括号。我们可能会想,那么这对括号应该如何放置呢?而事实上,我们应该这样想,那么n = 1..i-1
的结果,应该如何放到这对括号里呢?也就是这对括号,是不动的。- 我们只有两个地方可以放,一是这一对括号内部,二是括号外部。
外部: 其实放在括号左侧或者右侧都行。但是放在右侧,结果会按照字典序排列,因此我们选择放在右侧。但是要知道,放在左侧也是可以的。 - 我们要对所有
n < i
的情况遍历,要保证最后括号一共i
对。- 所以如果内部放
n = 0
的结果,右侧就要放n = i - 1
的结果,排列所有情况。 - 如果内部放
n = 1
的结果,右侧就要放n = i - 2
的结果,排列所有情况。 - 以此类推。。。
最后,内部放n = i - 1
的结果,右侧就要放n = 0
的结果,排列所有情况。
- 所以如果内部放
- 我们可以得出状态方程:
dp[i] = '(' + dp[k] + ')' + dp[i-1-k], k = 0..i-1
执行用时: 8 ms, 在所有 cpp 提交中击败了82.13%的用户
内存消耗: 9.9 MB, 在所有 cpp 提交中击败了97.08%的用户
// dp[0] = ""
// dp[i] = '(' + dp[k] + ')' + dp[i-1-k], k = 0..i-1 (按字典序)
// 或者dp[i] = dp[k] + '(' + dp[i-1-k] + ')', k = 0..i-1 (非字典序)
class Solution {
public:
vector<string> generateParenthesis(int n) {
vector< vector<string> > dp(n + 1, vector<string>());
dp[0] = {""};
for (auto i = 1; i < n + 1; ++ i) {
for (auto k = 0; k < i; ++ k) {
for (auto s1 : dp[k]) {
for (auto s2 : dp[i - 1 - k]) {
dp[i].push_back("(" + s1 + ")" + s2);
}
}
}
}
return dp[n];
}
};