LeetCode_String_22. Generate Parentheses 括號生成(C++/Java)【DFS、剪枝、括號匹配】
技術標籤:LeetCodeLeetCodeDFS回溯剪枝括號匹配中等難度String
目錄
一,題目描述
原題連結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 + ")");
}
};
可以看出時間和空間確實得到了優化*★,°*:.☆( ̄▽ ̄)/$:*.°★* 。