回溯法小結,簡單一招鮮


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);
    }

完~
回溯法告一段落,接下來嘗試動態規劃。


  1. Leetcode22——括號生成(回溯法) ↩︎

  2. Leetcode刷題java之39. 組合總和(回溯法以及回溯法框架). ↩︎

  3. 二進制手錶. ↩︎

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章