挑战刷leetcode第19天(回溯-电话号码的字母组合(17))

165 阅读4分钟

哪吒的递归大冒险:电话号码的字母组合

大家好,我是哪吒,今天我要带大家进入递归的世界,解决一个有趣的问题:电话号码的字母组合。这个问题看似简单,但背后隐藏着递归的魔力。我会用幽默搞笑的方式,带你一步步理解递归的实现思路和逻辑,同时用 Java 和 C++ 两种语言实现代码。准备好了吗?我们出发!


问题描述

image.png 想象一下,你手里有一部老式手机,键盘上每个数字键(2-9)对应几个字母。比如:

  • 2 对应 "abc"
  • 3 对应 "def"
  • ...
  • 9 对应 "wxyz"

现在给你一串数字,比如 "23",你需要返回所有可能的字母组合,比如 ["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"]。

听起来是不是有点像哪吒的混天绫,可以变幻出无数种形态?那我们来看看如何用递归来实现这个“变幻”过程。


递归的思路

递归就像哪吒的分身术,每次调用自己,都能解决一个小问题,最终拼凑出完整的答案。具体到这个问题,我们可以这样思考:

  1. 递归的终止条件:当组合的长度等于输入数字的长度时,说明我们已经找到一个完整的组合,可以把它加入结果列表。
  2. 递归的分解:对于当前数字对应的每个字母,我们把它加入当前组合,然后递归处理下一个数字。
  3. 回溯:每次递归结束后,我们需要“撤销”当前的选择,尝试下一个字母。

这个过程就像哪吒在战斗中不断尝试不同的招式,直到找到最合适的组合。


image.png

代码实现

Java 版本

import java.util.*;

public class LetterCombinations {
    public List<String> letterCombinations(String digits) {
        List<String> res = new ArrayList<>();
        if (digits == null || digits.length() == 0) {
            return res;
        }
        StringBuilder sb = new StringBuilder();
        Map<Character, String> map = new HashMap<>();
        map.put('2', "abc");
        map.put('3', "def");
        map.put('4', "ghi");
        map.put('5', "jkl");
        map.put('6', "mno");
        map.put('7', "pqrs");
        map.put('8', "tuv");
        map.put('9', "wxyz");
        dfs(res, sb, 0, digits, map);
        return res;
    }

    private void dfs(List<String> res, StringBuilder sb, int index, String digits, Map<Character, String> map) {
        // 终止条件:当前组合的长度等于输入数字的长度
        if (sb.length() == digits.length()) {
            res.add(sb.toString());
            return;
        }

        // 获取当前数字对应的字母
        String letters = map.get(digits.charAt(index));
        for (int i = 0; i < letters.length(); i++) {
            // 选择当前字母
            sb.append(letters.charAt(i));
            // 递归处理下一个数字
            dfs(res, sb, index + 1, digits, map);
            // 回溯:撤销选择
            sb.deleteCharAt(sb.length() - 1);
        }
    }

    public static void main(String[] args) {
        LetterCombinations solution = new LetterCombinations();
        List<String> result = solution.letterCombinations("23");
        System.out.println(result); // 输出: [ad, ae, af, bd, be, bf, cd, ce, cf]
    }
}

C++ 版本

#include <iostream>
#include <vector>
#include <unordered_map>

using namespace std;

class Solution {
public:
    vector<string> letterCombinations(string digits) {
        vector<string> res;
        if (digits.empty()) {
            return res;
        }
        string sb;
        unordered_map<char, string> map = {
            {'2', "abc"}, {'3', "def"}, {'4', "ghi"}, {'5', "jkl"},
            {'6', "mno"}, {'7', "pqrs"}, {'8', "tuv"}, {'9', "wxyz"}
        };
        dfs(res, sb, 0, digits, map);
        return res;
    }

private:
    void dfs(vector<string>& res, string& sb, int index, const string& digits,
             const unordered_map<char, string>& map) {
        // 终止条件:当前组合的长度等于输入数字的长度
        if (index == digits.size()) {
            res.push_back(sb);
            return;
        }

        // 获取当前数字对应的字母
        const string& letters = map.at(digits[index]);
        for (const char& letter : letters) {
            // 选择当前字母
            sb.push_back(letter);
            // 递归处理下一个数字
            dfs(res, sb, index + 1, digits, map);
            // 回溯:撤销选择
            sb.pop_back();
        }
    }
};

int main() {
    Solution solution;
    vector<string> result = solution.letterCombinations("23");
    for (const string& s : result) {
        cout << s << " ";
    }
    // 输出: ad ae af bd be bf cd ce cf
    return 0;
}

递归的思考

递归的核心在于分而治之。我们把一个大问题分解成小问题,然后通过递归调用解决小问题,最终拼凑出大问题的答案。在这个过程中,回溯是关键,它让我们能够尝试所有可能的组合。

就像哪吒在战斗中不断尝试不同的招式,递归也是在不断尝试不同的选择,直到找到最合适的组合。每一次递归调用,都是一个新的“分身”,它们共同协作,最终完成任务。


坚持的意义

写代码就像修炼法术,需要不断练习和坚持。递归看似复杂,但只要你理解了它的本质,就能轻松驾驭。就像哪吒从顽皮的小孩成长为强大的战士,我们也能通过不断学习,成为编程的高手。

所以,不要害怕递归,勇敢地去尝试吧!每一次递归调用,都是你成长的一步。坚持下去,你一定能掌握这门“法术”!


总结

今天我们通过电话号码的字母组合问题,学习了递归的实现思路和逻辑。我们用 Java 和 C++ 实现了代码,并通过幽默搞笑的方式,让大家对递归有了更深的理解。希望这篇文章能让你对递归产生兴趣,并愿意继续探索编程的奥秘。

我是哪吒,下次再见!记得坚持练习哦!🚀