22. 括号生成
难度 中等
数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
示例:
输入:n = 3
输出:[
"((()))",
"(()())",
"(())()",
"()(())",
"()()()"
]
广度优先遍历
借助队列的方式从1到n扩增结果集,每次对上一轮的所有括号组合 post 做如下操作:
- 外包一层括号
- 左边添加一个括号
- 右边添加一个括号
失败,扩增规则不能囊括所有有效结果,且存在重复项
补充:可以通过队列的方式模拟DFS思路
class Solution {
public:
//使用广度优先搜索时,需要结点自身维护信息,因此定义一个结点结构体
struct Node {
string str;
int left, right;
Node(string _str, int _left, int _right):str(_str), left(_left), right(_right) {}
};
vector<string> generateParenthesis(int n) {
vector<string> ans;
if (n == 0) return ans;
queue<Node> q;
q.push(Node("", n, n));
//每轮从队列中取出有效括号串的结点,尝试添加括号
while (!q.empty()) {
Node current = q.front(); q.pop();
//结点已经生成为有效结果,加入结果集
if (current.left == 0 && current.right == 0) ans.push_back(current.str);
if (current.left > 0)
q.push(Node(current.str + "(", current.left - 1, current.right));
if (current.right > 0 && current.left < current.right)
q.push(Node(current.str + ")", current.left, current.right - 1));
}
return ans;
}
};
深度优先遍历
从空串开始利用递归逐步构建有效的括号串,最终加入结果集
当括号对数为 n 时,转化为用于构建括号串的剩余左括号还剩 n 个,右括号还剩 n 个。每向下递归一层时,使用一个左括号或者右括号,加入当前括号串中,相应括号数量减少一个。当左右括号都使用完时,即构建了一个可能的结果
递归过程中需要剪枝排除无效的括号串——当前剩余可使用的括号中,右括号剩余的数量严格大于左括号剩余的数量,否则意味着构成的括号串中存在不成对的括号,括号串无效
class Solution {
public:
vector<string> ans;
vector<string> generateParenthesis(int n) {
if (n == 0) return ans;
DFS("", n, n);
return ans;
}
//当前递归结果,剩余可用的左括号,剩余可用的右括号
void DFS(string current, int left, int right) {
//使用完所有括号的有效结果添加到结果集中
if (left == 0 && right == 0) {
ans.push_back(current);
return;
}
//剪枝,去除违反生成规则的不合理的括号串
//剩余可用左括号严格大于有括号,说明当前符号串存在未完成闭合的右括号,生成括号串非法
if (left > right) return;
//从左向右添加左括号和右括号,试图生成有效括号串
if (left > 0) DFS(current + "(", left - 1, right);
if (right > 0) DFS(current + ")", left, right - 1);
}
};
(注:C++中向函数传递的参数默认是拷贝,应声明为传引用才能达到修改原对象的目的)
动态规划
该题所求的括号序列大致如下:
()
(()) ()()
((())) (()()) (())() ()(()) ()()()
...
对于 i = n 和 i = n - 1 两种情况, n 的可行解相比 n - 1 的可行解多出了一对新的括号
假设 i = n 的可行解中,最左端的左括号和其对应的右括号当做相比于 i = n - 1 时增加的括号,那么剩余部分的括号可能分布的位置有:
- 新增括号的内部,将这部分记为 P
- 新增括号的右侧,将这部分记为 Q
由此 i = n 时括号串的表达式可以记为:
"(" P ")" Q
将规则应用于实际的括号组合中:
//i = 0 的空串和 i = 1 的单个括号视为已知
//i = 2
(()) // P = "()", Q = ""
()() // P = "", Q = "()"
//i = 3
((())) // P = "(())", Q = ""
(()()) // P = "()()", Q = ""
(())() // P = "()", Q = "()"
()(()) // P = "", Q = "(())"
()()() // P = "", Q = "()()"
进一步我们可以发现,P和Q代表着 i = p , i = q 时构成的括号串的结果集,而 P 和 Q 的数目又满足 p + q = n - 1
由此我们可以使用动态规划求解括号串的结果集
/*
** 0.定义状态:dp[i]表示 括号数目为i时的结果集
** 1.状态转移方程:dp[i] = {"(" + dp[p] + ")" + dp[q]};
** 3.初始化:dp[0] = {""}, dp[1] = {"()"};
** 4.输出
** 5.空间优化
*/
class Solution {
public:
vector<string> generateParenthesis(int n) {
if (n == 0) return {};
if (n == 1) return { "()" };
//开辟dp数组
vector<vector<string>> dp(n+1);
dp[0] = { "" };
dp[1] = { "()" };
//从n = 2开始填写dp数组
for (int i = 2; i <= n; i++) {
for (int j = 0; j <i; j++) { //p从0到i-1遍历,q从i-1到0遍历
//取出结果集中所有括号串进行组合
for (string p : dp[j])
for (string q : dp[i - j - 1]) {
string str = "(" + p + ")" + q;
dp[i].push_back(str);
}
}
}
return dp[n];
}
};