LeetCode:17. 电话号码的字母组合

简介

题目链接:https://leetcode.cn/problems/letter-combinations-of-a-phone-number/description/

解决方式:字符串 + 回溯法(DFS、递归)/ 队列迭代法(BFS、队列)

回溯法

实现思路:

回溯法也就是递归,思路是很直接的思路,即得到数字对于的字符串后枚举每个字符的排列组合,只不过枚举的具体过程采用了深度优先搜索算法。

java 复制代码
class Solution {
    // 结果集合
    private List<String> answer = new ArrayList<>();
    // 哈希映射
    private Map<Character, String> map = new HashMap<>(){
        {
            put('2', "abc");
            put('3', "def");
            put('4', "ghi");
            put('5', "jkl");
            put('6', "mno");
            put('7', "pqrs");
            put('8', "tuv");
            put('9', "wxyz");
        }
    };

    public List<String> letterCombinations(String digits) {
       // 边界处理
       if(digits == "" || digits.length() == 0) return answer;
       // 回溯法(递归)
       backtrack(digits, 0, new StringBuilder());
       // 返回结果
       return answer;
    }

    private void backtrack(String digits, int index, StringBuilder path){
        // 递归终止条件
        if(index == digits.length()){
            answer.add(path.toString());
            return;
        }
        // 递归
        // DFS排列组合字母
        char digit = digits.charAt(index);
        String letter = map.get(digit);
        for(int i = 0; i < letter.length(); i++){
            path.append(letter.charAt(i));
            backtrack(digits, index + 1, path);
            path.deleteCharAt(path.length() - 1);
        }
    }
}

算法执行流程:


1. 初始化

  • res 为空列表,用于存放最终结果。
  • map 定义了按键 '2'"abc"'3'"def"
  • 调用 letterCombinations("23")digits 不为空,进入 backtrack(digits, 0, new StringBuilder())

2. 第一次进入 backtrack(index = 0)

  • index = 0digits.length() = 2,不相等,继续。
  • 获取 digit = digits.charAt(0) = '2'
  • letters = map.get('2') = "abc"
  • 遍历 "abc" 的每个字符:

第一层循环(i = 0,字母 'a'

  • path.append('a')path 变为 "a"
  • 递归调用 backtrack(digits, 1, path)

3. 第二次进入 backtrack(index = 1)

  • index = 1,不等于长度 2。
  • digit = digits.charAt(1) = '3'
  • letters = "def"
  • 遍历 "def" 的每个字符:

第二层循环(i = 0,字母 'd'

  • path.append('d')path 变为 "ad"
  • 递归调用 backtrack(digits, 2, path)
第三次进入 backtrack(index = 2)
  • index = 2,等于 digits.length(),将 path.toString() = "ad" 加入 res
  • 返回到上一层(index = 1,刚执行完 backtrack(digits, 2, path) 的那一行)。

回溯(在 index = 1 的循环内)

  • 执行 path.deleteCharAt(path.length() - 1),删除最后一个字符 'd'path 恢复为 "a"

继续第二层循环(i = 1,字母 'e'

  • path.append('e')"ae"
  • 递归调用 backtrack(digits, 2, path)
  • 此时 index = 2,加入 "ae"res
  • 回溯删除 'e'path 恢复 "a"

继续第二层循环(i = 2,字母 'f'

  • path.append('f')"af"
  • 递归调用,加入 "af"res
  • 回溯删除 'f'path 恢复 "a"

第二层循环结束,返回到第一层(index = 0,i = 0 的那个递归调用之后)。

4. 回溯到第一层循环(i = 0)

  • 执行 path.deleteCharAt(path.length() - 1),删除 'a'path 变为空。

第一层循环(i = 1,字母 'b'

  • path.append('b')"b"
  • 递归调用 backtrack(digits, 1, path)
进入 index = 1 的递归:
  • 遍历 "def",依次得到 "bd""be""bf",加入 res
  • 回溯删除 'b'path 空。

第一层循环(i = 2,字母 'c'

  • 同理,生成 "cd""ce""cf",加入 res

5. 最终结果

res 中包含 ["ad","ae","af","bd","be","bf","cd","ce","cf"],按任意顺序返回。

核心思想总结

  • 深度优先搜索(DFS):从第一个数字开始,依次为每个数字选择一个字母,组合成字符串。
  • 回溯:每完成一个完整组合(到达叶子节点)后,撤销最后一步的选择,尝试下一个分支。
  • 路径维护 :使用 StringBuilder 动态构建当前组合,避免频繁创建新字符串。

这就是回溯法解决该题目的完整流程。


队列迭代法

实现思路:

队列迭代法采取的是广度优先搜索算法,即层次构建的思路。通过队列的循环入队出队操作,排列组合每一个字符组合。

java 复制代码
class Solution {
    public List<String> letterCombinations(String digits) {
        // 结果集合
        List<String> ans = new ArrayList<>();
        // 边界处理
        if(digits == "" || digits.length() == 0) return ans;
        // 初始化队列和字符串数组
        String[] map = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
        Queue<String> queue = new LinkedList<>();
        // 添加空字符串,方便后续添加字符
        queue.offer("");

        // BFS遍历字符
        for(int i = 0; i < digits.length(); i++){
            int index = digits.charAt(i) - '0';
            String letter = map[index];
            // 迭代队列
            int size = queue.size();
            for(int j = 0; j < size; j++){
                String prefix = queue.poll();
                for(char c : letter.toCharArray()){
                    queue.offer(prefix + c);
                }
            }
        }
        // 转换
        ans.addAll(queue);
        // 返回结果
        return ans;
    }
}

算法执行流程:


初始状态

队列 queue = [""](一个空字符串,作为起始前缀)。


处理第一个数字 '2'

  • letters = "abc"
  • 当前队列大小 size = 1
  • 弹出 prefix = ""
    • 遍历 'a'"" + 'a' = "a" 入队 → 队列变为 ["a"]
    • 遍历 'b'"b" 入队 → 队列变为 ["a", "b"]
    • 遍历 'c'"c" 入队 → 队列变为 ["a", "b", "c"]
  • 第一轮循环结束,队列中为 ["a", "b", "c"]

处理第二个数字 '3'

  • letters = "def"
  • 当前队列大小 size = 3(注意:这个大小是在本轮循环开始前记录的,即上一轮结束时的队列长度)
  • 依次弹出三个元素:
弹出 "a"
  • 拼接 'd'"ad" 入队 → 队列:["b", "c", "ad"]
  • 拼接 'e'"ae" 入队 → 队列:["b", "c", "ad", "ae"]
  • 拼接 'f'"af" 入队 → 队列:["b", "c", "ad", "ae", "af"]
弹出 "b"
  • 拼接 'd'"bd" 入队 → 队列:["c", "ad", "ae", "af", "bd"]
  • 拼接 'e'"be" 入队 → 队列:["c", "ad", "ae", "af", "bd", "be"]
  • 拼接 'f'"bf" 入队 → 队列:["c", "ad", "ae", "af", "bd", "be", "bf"]
弹出 "c"
  • 拼接 'd'"cd" 入队 → 队列:["ad", "ae", "af", "bd", "be", "bf", "cd"]
  • 拼接 'e'"ce" 入队 → 队列:["ad", "ae", "af", "bd", "be", "bf", "cd", "ce"]
  • 拼接 'f'"cf" 入队 → 队列:["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"]

处理结束

  • 队列中的内容为:["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"]
  • 这就是所有字母组合。

核心思想总结

  • 广度优先搜索(BFS):每一轮循环处理一个数字,将当前所有部分组合(队列中的字符串)扩展为更长的组合。
  • 逐层构建 :初始队列只有空串,处理完第 1 个数字后队列长度为 3("a","b","c"),处理完第 2 个数字后队列长度变为 9,即所有 3×3 的组合。
  • 空间换时间:队列中会同时存储所有中间状态的组合,但最终结果也来自同一个队列。
相关推荐
JieE21221 小时前
LeetCode 101. 对称二叉树|JS 递归 + 迭代双解法,彻底搞懂镜像判断
javascript·算法
JieE2122 天前
LeetCode 56. 合并区间|超清晰 JS 图解思路,面试高频区间题
javascript·算法·面试
Jack202 天前
HarmonyOS开发中错误处理策略:网络异常统一处理
算法
小小杨树2 天前
读懂色彩:拍照调色不再难
算法·计算机视觉·配色
JieE2123 天前
LeetCode 226. 翻转二叉树|JS 递归超详细拆解,二叉树入门经典题
javascript·算法
JieE2123 天前
LeetCode 104. 二叉树的最大深度|递归思路超详细拆解
javascript·算法
vivo互联网技术3 天前
CVPR 2026 | 全新强化学习框架 BeautyGRPO:重塑真实人像
算法·大模型·cvpr·影像
Darling噜啦啦3 天前
列表转树算法深度解析:从 Map 到 Reduce 的两种实现,面试高频考点
数据结构·算法·面试
用户497863050733 天前
(一)小红的数组操作
算法·编程语言