题目来源
题目描述
class Solution {
public:
vector<string> generateParenthesis(int n) {
}
};
题目解析
思路一
分析
如果去除题目中的“有效性”,只考虑组合,那么这个体就变得容易多了。也就是有n个(
和n个)
,有2n
个格子,写出所有可能的组合
填入第1个空位后,需要考虑第2个空位需要填什么,
填入第2个空位后,需要考虑第3个空位需要填什么,
填入第3个空位后,需要考虑第4个空位需要填什么,
…
我们发现,存在相似性的子问题,那么可以使用递归解决。在2n
个格子填满的时候,递归终止。也就是上图中,在树的第n层递归终止
#include <iostream>
#include <vector>
using namespace std;
class Solution {
std::vector<std::string> ans;
void helper(int level, const std::string &str){
if(level == 0){
std::cout << str << "\t";
ans.push_back(str);
return;
}
helper(level - 1, str + "(");
helper(level - 1, str + ")");
}
public:
vector<string> generateParenthesis(int n) {
helper(2 * n, "");
return ans;
}
};
以上代码运行后,结果集为:
[“((((((”,“((((()”,“(((()(”,“(((())”,“((()((”,“((()()”,“((())(”,“((()))”,“(()(((”,“(()(()”,“(()()(”,“(()())”,“(())((”,“(())()”,“(()))(”,“(())))”,“()((((”,“()((()”,“()(()(”,“()(())”,“()()((”,“()()()”,“()())(”,“()()))”,“())(((”,“())(()”,“())()(”,“())())”,“()))((”,“()))()”,“())))(”,“()))))”,“)(((((”,“)(((()”,“)((()(”,“)((())”,“)(()((”,“)(()()”,“)(())(”,“)(()))”,“)()(((”,“)()(()”,“)()()(”,“)()())”,“)())((”,“)())()”,“)()))(”,“)())))”,“))((((”,“))((()”,“))(()(”,“))(())”,“))()((”,“))()()”,“))())(”,“))()))”,“)))(((”,“)))(()”,“)))()(”,“)))())”,“))))((”,“))))()”,“)))))(”,“))))))”]
递归并“剪枝”解法
上面的解法中存在大量的无效括号组合,我们需要去除无效的组合,也就是剪掉不合法的分支。
不合法的分支如何判断呢?
- 某个空位置可以放
(
的条件是,(
有余量,也就是使用的(
数量 < n,或者说剩余的(
数量 > 0 - 某个空为止可以放
)
的条件时,已经使用的(
数量大于)
的数量,或者说剩余的)
大于剩余的(
class Solution {
std::vector<std::string> ans;
void helper(int left, int right, int level, const std::string &str){
if(level == 0){
ans.push_back(str);
return;
}
if(left > 0){
helper(left - 1, right, level - 1, str + "(");
}
if(right > left){
helper(left, right - 1, level - 1, str + ")");
}
}
public:
vector<string> generateParenthesis(int n) {
helper(n, n, 2 * n, "");
return ans;
}
};
回溯算法三要点
- 选择:
- 本题,每次最多两个选择,选择
(
或者)
,“选择”会展开出一颗空间树 - 用DFS遍历这棵树,找出所有的解,这个过程叫做回溯
- 本题,每次最多两个选择,选择
- 约束条件:
- 即:什么时候可以选
(
,什么时候可以选)
- 利用约束“剪枝”,
- 即:什么时候可以选