Leetcode22——括號生成(回溯法)
1. 寫在前面
回溯[sù]:意思是逆着水流的方向走、逆水而行,逆流而上1。我一直讀成[shuò]。
框[kuàng]架:
1.建築工程中,由樑、柱等聯結而成的結構:完成主體~工程。
2.比喻事物的基本組織、結構:這部長篇小說已經有了一個大致的~。
得,語文沒學好。
2. 回溯法基本思想
在許多遞歸問題當中,我們採取的方法都是窮盡所有的可能,從而找出合法的解。但是在某些情況下,當遞歸到某一層的時候,根據設置的判斷條件,可以 judge 此解是不合法的。在這種情況下,我們就沒必要再進行深層次的遞歸,從而可以提高算法效率。這一類算法我們稱爲“回溯法”,設置的判斷條件稱爲“剪枝函數”。2
個人覺得啊,回溯法可以類比爲一棵自上而下的樹,每一次滿足題目要求的選擇就是一個節點。當滿足題目結束條件時,根到當前葉子節點的路徑就是一個可行解。
3. 回溯法基本框架
回溯法框架3
1、路徑:也就是已經做出的選擇。
2、選擇列表:也就是你當前可以做的選擇。
3、結束條件:也就是到達決策樹底層,無法再做選擇的條件。
代碼方面,回溯算法的框架:
result = []
def backtrack(路徑, 選擇列表):
if 滿足結束條件:
result.add(路徑)
return
for 選擇 in 選擇列表:
做選擇
backtrack(路徑, 選擇列表)
撤銷選擇
其核心就是 for 循環裏面的遞歸,在遞歸調用之前「做選擇」,在遞歸調用之後「撤銷選擇」,特別簡單。
4. 題目描述
- 括號生成
數字 n 代表生成括號的對數,請你設計一個函數,用於能夠生成所有可能的並且 有效的 括號組合。
示例:
輸入:n = 3
輸出:[
“((()))”,
“(()())”,
“(())()”,
“()(())”,
“()()()”
]
5. 代碼
參考回溯法框架三點的解題。
- 1.路徑:也就是已經做出的選擇。 ->這裏使用StringBuilder保存已走路徑,方便到時候回溯 — stringBuilder.deleteCharAt(X);
- 2.選擇列表:也就是你當前可以做的選擇。 ->每次都可以選擇 左括號 或者 右括號,但有限制條件:1.左右括號數量均不能超過n。2.當前StringBuilder的右括號數量必須小於左括號數量。
- 3.結束條件:也就是到達決策樹底層,無法再做選擇的條件。->StringBuilder長度爲2*n。
List<String> result = new ArrayList<String>();
public List<String> generateParenthesis(int n) {
//1、路徑:也就是已經做出的選擇。
StringBuilder stringBuilder = new StringBuilder();
//本題有兩個需要注意:
// 1.左括號和右括號數目均不能超過n
// 2.如何判斷括號合法?————————————————————當前已保存的括號數關係需滿足:right括號數 <= left括號數。
backTrack(n, 0, 0, stringBuilder);
return result;
}
/**
* 回溯計算
*
* @param num 括號對數
* @param leftNum 已有左括號數量
* @param rightNum 已有右括號數量
* @param stringBuilder 已保存的路徑
*/
private void backTrack(int num, int leftNum, int rightNum, StringBuilder stringBuilder) {
//結束條件
if (stringBuilder.length() >= num * 2) {
result.add(stringBuilder.toString());
return;
}
//做選擇
if (leftNum < num) {
stringBuilder.append("(");
backTrack(num, ++leftNum, rightNum, stringBuilder);
//撤銷選擇
stringBuilder.deleteCharAt(stringBuilder.length() - 1);
--leftNum;
}
//咱再做一次選擇
if (rightNum < leftNum) {
stringBuilder.append(")");
backTrack(num, leftNum, ++rightNum, stringBuilder);
//撤銷選擇
stringBuilder.deleteCharAt(stringBuilder.length() - 1);
--rightNum;
}
}
6. 總結
一開始做題用的是暴力遞歸法,當括號總數達到2*n時才判斷括號是否有效"((((((",看了題解之後才知道回溯法。回溯法題目做的還是不多,得多刷幾題找找感覺。加油!
/**
* 判斷括號是否有效
* @param array char數組
* @return 是否有效
*/
private boolean parenthesIsValid(char[] array) {
int balance = 0;
for (char c : array) {
if ('(' == c) {
balance++;
} else {
balance--;
}
if (balance < 0) {
return false;
}
}
return balance == 0;
}
完~