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 的组合。
  • 空间换时间:队列中会同时存储所有中间状态的组合,但最终结果也来自同一个队列。
相关推荐
山楂树の1 小时前
广度优先搜索 (BFS)
算法·广度优先·宽度优先
say_fall1 小时前
深入理解Linux内核进程调度:从基础概念到O(1)调度算法
linux·运维·服务器·算法·计算机组成
拂拉氏1 小时前
【知识讲解-题目讲解】算法系列之动态规划入门(上)
算法·leetcode·动态规划
菜菜的顾清寒1 小时前
力扣HOT100(46)将有序数组转换为二叉搜索树
算法·leetcode·职场和发展
随意起个昵称9 小时前
区间dp-基础题目1(石子合并)
算法·动态规划
吞下星星的少年·-·10 小时前
线段树模板
算法
段一凡-华北理工大学10 小时前
2026 高炉炼铁智能化技术全景与演进路径~系列文章11:演进路径与行业未来
大数据·网络·人工智能·算法·工业智能体·高炉炼铁智能化
凯瑟琳.奥古斯特10 小时前
高阶子查询题目精炼
开发语言·数据库·python·职场和发展·数据库开发
叶小鸡10 小时前
小鸡玩算法-力扣HOT100-多维动态规划
算法·leetcode·动态规划