每日一道 LeetCode (54):電話號碼的字母組合

每天 3 分鐘,走上算法的逆襲之路。

前文合集

每日一道 LeetCode 前文合集

代碼倉庫

GitHub: https://github.com/meteor1993/LeetCode

Gitee: https://gitee.com/inwsy/LeetCode

題目:電話號碼的字母組合

難度:中等

題目來源:https://leetcode-cn.com/problems/letter-combinations-of-a-phone-number/

給定一個僅包含數字 2-9 的字符串,返回所有它能表示的字母組合。

給出數字到字母的映射如下(與電話按鍵相同)。注意 1 不對應任何字母。

示例:

輸入:"23"
輸出:["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"].

說明:
儘管上面的答案是按字典序排列的,但是你可以任意選擇答案輸出的順序。

解題思路

這道題看着有木有感覺很簡單的樣子,至少我一開始是覺得蠻簡單的。

先使用哈希表定義一個字典:

static final Map<Character, String> map = Map.of(
    '2', "abc", '3', "def", '4', "ghi", '5', "jkl",
    '6', "mno", '7', "pqrs", '8', "tuv", '9', "wxyz"
);

上面這個是在 JDK9 以後開始支持的初始化方式,正好 LeetCode 也支持 JDK9 的語法,如果一定要用 JDK8 的話,emmmmmmmm:

Map<Character, String> map = new HashMap<Character, String>() {{
    put('2', "abc");
    put('3', "def");
    put('4', "ghi");
    put('5', "jkl");
    put('6', "mno");
    put('7', "pqrs");
    put('8', "tuv");
    put('9', "wxyz");
}};

別問我爲啥要用 JDK9 的寫法,問就是省紙。

接下來思考那個示例 「23」 ,首先程序先解析 2 , 2 對應了三個字母 abc , 3 也對應了三個字母 def ,這道題在最終輸出的結果集沒玩什麼滑頭,直接就是所有的情況都排列出來就可以了,那麼我可以套兩個循環,直接把所有的情況全都迭代出來。

但是題目上並沒有說輸入的字符串一定是兩位的,如果是三位的那不就傻了。實際上是輸出的字符串有幾位就需要套幾層循環,好像不是很好寫啊。。。

這個時候,就需要用到一種不是很好理解,並且不是很好寫的方案了——遞歸。

使用遞歸的時候並不需要指定遞歸的次數,遞歸是直接遞歸到底的。

於是就有了下面這段代碼:

// 最終結果
private List<String> res = new ArrayList<> ();
// 組合形式
private StringBuilder sb = new StringBuilder();

public List<String> letterCombinations(String digits) {
    if (digits.length() == 0) return res;
    backtrack(digits, 0);
    return res;
}

// 回溯函數
public void backtrack(String digits, int index) {
    if (index == digits.length()) {
        res.add(sb.toString());
    } else {
        char digit = digits.charAt(index);
        String letters = map.get(digit);
        int lettersCount = letters.length();
        for (int i = 0; i < lettersCount; i++) {
            sb.append(letters.charAt(i));
            backtrack(digits, index + 1);
            sb.deleteCharAt(sb.length() - 1);
        }
    }
}

整段代碼並不長,而且看起來還很好理解,整體最核心的就是在那個 for 循環裏面的三句話:

sb.append(letters.charAt(i));
backtrack(digits, index + 1);
sb.deleteCharAt(sb.length() - 1);

第一句話是先把當前的值放入到我們的全局對象 sb 中,按照案例的 「23」 來演示的話就是先把 a 放到 sb 中,然後開始遞歸進下一次,這時候,我們第一個字符串全都是 a ,第二個字符串 3 對應的可選值有 def ,接着我們把 d 也放到 sb 中,這時 sb 中的值是 ad ,然後進入下一次迭代,走到上面的那個判斷,把 sb 中的 ad 放入最終的輸出結果 res 中,然後遞歸往上走一層,刪除 sb 中的最後一個數,這時 sb 中剩下的是 a , for 循環進入下一次循環 ,像 sb 中添加一個 e ,然後重複上面的過程。

上面這段解釋有點繞,可以多讀幾次,或者在代碼上多打幾個斷點手動 debug 一下,分分鐘就懂了。

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