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];
}
};