题目
https://leetcode-cn.com/problems/generate-parentheses/
思路1——dfs
本题在暴力破解(列出所有情况)的基础上很容易想到回溯和剪枝,由于涉及到递归,有些难度。先上一个其他大神给的深度优先遍历的模板:
void dfs()//参数用来表示状态
{
if(到达终点状态)
{
...//根据题意添加
return;
}
if(越界或者是不合法状态)
return;
if(特殊状态)//剪枝
return ;
for(扩展方式)
{
if(扩展方式所达到状态合法)
{
修改操作;//根据题意来添加
标记;
dfs();
(还原标记);
//是否还原标记根据题意
//如果加上(还原标记)就是 回溯法
}
}
}
实际上这也是递归的模板。利用这个思路不断尝试添加左括号和右括号可以得到剪枝条件。
剪枝对于新手来言确实有难度,一个好的方法就是自己举一个简单例子慢慢试,比如假设有两个左括号和两个右括号,按照下图尝试得到剪枝条件。
根据以上思路可以得到代码:
class Solution {
public:
vector<string> res;
void dfs(string parenthesis,int left,int right){
//参数:当前字符串,左括号剩余数量,右括号剩余数量
if(left==0&&right==0){ //递归结束
res.push_back(parenthesis);
return;
}
if(left>0){ //满足剪枝条件1-尝试加入(
dfs(parenthesis+'(',left-1,right);
}
if(right>left){ //满足剪枝条件2-尝试加入)
dfs(parenthesis+')',left,right-1);
}
}
vector<string> generateParenthesis(int n) {
string parenthesis="";
dfs(parenthesis,n,n);
return res;
}
};
代码很简单,值得一提的是dfs(parenthesis+'(',left-1,right);这一句是下面三句的简化版:
parenthesis.push_back('(');
dfs(parenthesis,left-1,right);
parenthesis.pop_back();
前者比较高明,将需要回溯的值加入到参数,值得学习。
思路2——dp
动态规划的思路确实难想,不过一道题有多种解法都研究透了才能举一反三。动态规划的做法像数学归纳法。对本题已知n-1对括号的排列组合情况,求n对时的情况。就是在n-1对括号排列好的情况下,怎么加入一对括号。
对本题除了n=0的情况,无论什么排列第一个括号肯定是'(',所以这个就是第n对括号的左括号放置位置(这个很难想),那么右括号就是向右不断遍历,这样得到的情况就是dp[n]。
附上代码大家好好思考:
//dp做法
//已知n-1对括号的排列组合情况,求第n对情况
class Solution{
public:
vector<string> generateParenthesis(int n){
if(n==0) return {};
if(n==1) return {"()"};
vector<vector<string>> dp(n+1);
dp[0]={""};
dp[1]={"()"};
for(int i=2;i<=n;i++){ //从n至少为2开始
for(int j=0;j<i;j++){
for(string p:dp[j]){
for(string q:dp[i-j-1]){
string temp="("+p+")"+q; //左括号一定在最左边
dp[i].push_back(temp);
}
}
}
}
return dp[n];
}
};