目录
一,题目描述
原题链接https://leetcode-cn.com/problems/generate-parentheses/
英文描述
Given n pairs of parentheses, write a function to generate all combinations of well-formed parentheses.
Example 1:
Input: n = 3
Output: ["((()))","(()())","(())()","()(())","()()()"]
Example 2:Input: n = 1
Output: ["()"]
Constraints:
1 <= n <= 8
中文描述
数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
示例:
输入:n = 3
输出:[
"((()))",
"(()())",
"(())()",
"()(())",
"()()()"
]
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/generate-parentheses
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
二,解题思路
在DFS寻找所有可能结果的同时进行【剪枝】、【判断是否符合条件】。
- n表示完美匹配的括号数目,而且必须先有左括号,才能出现右括号;
- 利用numOfLeft记录左括号数目,也就是说numOfLeft满足【numOfLeft < 0 || numOfLeft > n】,超出这个范围的字符串,就一定不能完美匹配,因此可以通过剪枝pass掉;
- 当字符串长度顺利达到2n时,只需要判断左括号数目是否达到n,即可判定字符串是否满足条件;
于是,优雅的题解代码便诞生了o(* ̄▽ ̄*)ブ
三,AC代码
C++
class Solution {
public:
vector<string> ans;
vector<string> generateParenthesis(int n) {
dfs(n, 0, "");
return ans;
}
void dfs(int n, int numOfLeft, string tem) {
// 字符串长度达到2n标准
if(tem.size() >= 2 * n) {
// 左括号与右括号完美匹配
if(numOfLeft == 0) ans.push_back(tem);
return;
}
// 左括号数目超出能够匹配的范围
if(numOfLeft < 0 || numOfLeft > n) return;
// 左括号数目加一
dfs(n, numOfLeft + 1, tem + "(");
// 左右括号抵消,左括号数目减一
dfs(n, numOfLeft - 1, tem + ")");
}
};
Java
由于Java处理字符串比较麻烦,代码细节部分处理与C++有较大不同
class Solution {
List<String> ans = new ArrayList<String>();
public List<String> generateParenthesis(int n) {
// java中字符串是常量,不能直接改动,需要借助StringBuilder进行字符串操作
dfs(n, 0, new StringBuilder());
return ans;
}
public void dfs(int n, int numOfLeft, StringBuilder tem) {
// 字符串长度达到2n标准
if(tem.length() >= 2 * n) {
// 左括号与右括号完美匹配
try {
// 不使用try/catch,这里直接判断会报空指针异常
if(numOfLeft == 0) ans.add(tem.toString());
} catch(Exception e) {
} finally {
return;
}
}
// 左括号数目超出能够匹配的范围
if(numOfLeft < 0 || numOfLeft > n) return;
// 左括号数目加一
tem.append("("); // 入栈
dfs(n, numOfLeft + 1, tem);
tem.deleteCharAt(tem.length() - 1); // 出栈
// 左右括号抵消,左括号数目减一
tem.append(")"); // 入栈
dfs(n, numOfLeft - 1, tem);
tem.deleteCharAt(tem.length() - 1); // 入栈
}
}
四,解题过程
第一博
利用DFS获取并判断所有可能的解。
- 利用DFS遍历所有可能的括号组合,并根据左括号数目的限制(不能小于0或大于n)进行剪枝;
- DFS过程中,一旦字符串长度达到标准2n,进入判定函数判断括号是否完美匹配;
- 若完美匹配,则将其存入结果集中;
class Solution {
public:
vector<string> ans;
vector<string> generateParenthesis(int n) {
dfs(n, 0, "");
return ans;
}
void dfs(int n, int numOfLeft, string tem) {
if(tem.size() >= 2 * n) {
if(isLegal(tem)) {
ans.push_back(tem);
}
return;
}
if(numOfLeft < 0 || numOfLeft > n) return;
dfs(n, numOfLeft + 1, tem + "(");
dfs(n, numOfLeft - 1, tem + ")");
}
bool isLegal(string s) {
int leftBracketNum = 0;
for(int i = 0; i < s.size(); i++) {
if(s.substr(i, 1) == "(") leftBracketNum++;
else {
if(leftBracketNum == 0) return false;
leftBracketNum--;
}
}
return leftBracketNum == 0 ? true : false;
}
};
拉跨(;′⌒`)
第二搏
第一搏中虽然尽可能的进行剪枝,但是每次利用函数对字符串进行合法判断,性能确实影响较大。
考虑到能够利用【左括号数目限制配对】,以及【DFS过程中优先选择左括号入栈】,是否可以省去判断字符串是否满足条件的步骤?
当然是可以的。
由于左括号入栈(把字符添加到tem末尾当作入栈),numOfLeft加一,右括号入栈,numOfLeft减一。如果完美匹配,那么在字符串长度达到2n时,numOfLeft必定为0
class Solution {
public:
vector<string> ans;
vector<string> generateParenthesis(int n) {
dfs(n, 0, "");
return ans;
}
void dfs(int n, int numOfLeft, string tem) {
// 字符串长度达到2n标准
if(tem.size() >= 2 * n) {
// 左括号与右括号完美匹配
if(numOfLeft == 0) ans.push_back(tem);
return;
}
// 左括号数目超出能够匹配的范围
if(numOfLeft < 0 || numOfLeft > n) return;
// 左括号数目加一
dfs(n, numOfLeft + 1, tem + "(");
// 左右括号抵消,左括号数目减一
dfs(n, numOfLeft - 1, tem + ")");
}
};
可以看出时间和空间确实得到了优化*★,°*:.☆( ̄▽ ̄)/$:*.°★* 。