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 = 0,digits.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 的组合。
  • 空间换时间:队列中会同时存储所有中间状态的组合,但最终结果也来自同一个队列。
相关推荐
Rabitebla8 分钟前
【系统性学习笔记】Linux 权限篇(二):文件属性、权限修改与目录权限的底层逻辑
linux·c++·笔记·学习·算法
远离UE419 分钟前
半拉格朗日学习笔记 动力学 实时渲染
人工智能·算法
小郎君65827 分钟前
MPTT 不是万能编号:一棵百万节点树的增删改与批量事务
算法
FPGA小徐35 分钟前
【一生一芯 / PA】AM 核心文件逐行解析(上):从 start.S 到 gpu.c
前端·算法
Tairitsu_H1 小时前
[动态规划#1] 子数组 | 最大子数组和 | 环形子数组的最大和 | 乘积最大子数组 | 乘积为正数的最长子数组
算法·动态规划
凯瑟琳.奥古斯特2 小时前
动态调优IoU提升OCR文本合并精度
开发语言·python·算法
布莱克6052 小时前
A* 算法详解:从原理到实现
开发语言·c++·算法
段一凡-华北理工大学2 小时前
高炉炼铁机器视觉与智能识别十八讲~系列文章15:可靠性篇:防误报、容错与识别系统的鲁棒性设计
人工智能·算法·机器学习·高炉大模型·高炉机器视觉·高炉炼铁模型开发
某林2123 小时前
局部路径规划器 DWB:速度采样与评分权重的取值分析
人工智能·算法·架构·机器人·技术复盘
AI职业加油站3 小时前
模数共振行动解读|大模型应用工程师证书,匹配行业 RAG/Agent 落地人才需求
人工智能·学习·算法·职场和发展·职场发展