1. 前言
Leetcode上也就做了小10道回溯法題目,都是挑簡單/中等的題目做的。談不上幫大家深入此類題目解答,只是分享自己做題心得。
2. 思想
回溯法,我把它稱爲“聰明窮舉法”,它的思想可以總結爲:“遞歸+剪枝+取消已做的選擇”1。一般遞歸是窮舉所有可能的情況,最後判斷是否符合題目要求。一般遞歸缺點是會出現明顯不符合題意的結果(如Leetcode22題會出現"((((((“的情況)。而回溯法也有用到遞歸,但它的"聰明"之處在於——1.遞歸過程中如果發現已經不符合題目要求了,則不繼續進行。2.同時,A選擇遞歸完成之後,它還會取消遞歸前做的A選擇,而改用B,C…選擇繼續遞歸。
如Leetcode22,當輸入參數爲3時,表示需要3對括號,也就是3個左括號,3個右括號。那麼遞歸過程中,剪枝條件爲:1.左括號大於等於3 2.右括號大於等於左括號時,就觸發剪枝條件,直接return,不再往下遞歸,也就不會生成如”(((((("這樣的結果。
3. 一招鮮框架
回溯法框架2
1、路徑:也就是已經做出的選擇。
2、選擇列表:也就是你當前可以做的選擇。
3、結束條件:也就是到達決策樹底層,無法再做選擇的條件。
代碼方面,回溯算法的框架:
result = []
void backtrack(路徑, 選擇列表):
if 滿足結束條件{
result.add(路徑)
return
}
for 選擇 in 選擇列表{
做選擇
backtrack(路徑, 選擇列表)
撤銷選擇
}
其核心就是 for 循環裏面的遞歸,在遞歸調用之前「做選擇」,在遞歸調用之後「撤銷選擇」,特別簡單。
4. 一個有意思的手錶問題
401. 二進制手錶
二進制手錶頂部有 4 個 LED 代表小時(0-11),底部的 6 個 LED 代表分鐘(0-59)。
每個 LED 代表一個 0 或 1,最低位在右側。
例如,上面的二進制手錶讀取 “3:25”。
給定一個非負整數 n 代表當前 LED 亮着的數量,返回所有可能的時間。
案例:
輸入: n = 1
返回: ["1:00", "2:00", "4:00", "8:00", "0:01", "0:02", "0:04", "0:08", "0:16", "0:32"]
注意事項:
輸出的順序沒有要求。
小時不會以零開頭,比如 “01:00” 是不允許的,應爲 “1:00”。
分鐘必須由兩位數組成,可能會以零開頭,比如 “10:2” 是無效的,應爲 “10:02”。
題目來源Leetcode 401題3。
咱直接套一招鮮框架:
1、路徑:也就是已經做出的選擇。 -> 咱用一個boolean[] selected數據表示10個LED燈哪些亮了;變量hour,minute表示已做的選擇已經有多少小時和分鐘。
2、選擇列表:也就是你當前可以做的選擇。 ->從start下標開始做選擇(防止重複選擇),且需要selected[i]爲false。
3、結束條件:也就是到達決策樹底層,無法再做選擇的條件。 ->已亮燈的數量大於等於傳入的參數數量。
3、剪枝條件:與題意明顯不符的情況。 -> hour取值範圍[0,11],minute取值範圍[0,59]。
以下是代碼:
List<String> results = new ArrayList<>();
public List<String> readBinaryWatch(int num) {
boolean[] isSelected = new boolean[10];
backTrack(num, isSelected, 0, 0, 0, 0);
return results;
}
/**
* @param num 亮燈的數量
* @param isSelected 是否亮燈數組
* @param selectedNum 已亮燈的數量
* @param hour 小時
* @param minute 分鐘
* @param start 起始下標
*/
private void backTrack(int num, boolean[] isSelected, int selectedNum, int hour, int minute, int start) {
if (num == selectedNum) {
results.add(hour + ":" + String.format("%02d", minute));
return;
}
for (int i = start; i < isSelected.length; i++) {
if (isSelected[i]) {
continue;
}
//剪枝 小時[0,11],分鐘[0,59]
if (i <= 3) {
//小時
if (hour + Math.pow(2, i) <= 11) {
isSelected[i] = true;
backTrack(num, isSelected, selectedNum + 1, (int) (hour + Math.pow(2, i)), minute, i);
isSelected[i] = false;
}
} else {
//分鐘
if (minute + Math.pow(2, i - 4) <= 59) {
isSelected[i] = true;
backTrack(num, isSelected, selectedNum + 1, hour, (int) (minute + Math.pow(2, i - 4)), i);
isSelected[i] = false;
}
}
}
}
@Test
public void test() {
List<String> strings = this.readBinaryWatch(2);
System.out.println(strings.size());
System.out.println(strings);
}
完~
回溯法告一段落,接下來嘗試動態規劃。