17. 电话号码的字母组合

17. 电话号码的字母组合

中等

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。

给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

示例 1:

复制代码
输入:digits = "23"
输出:["ad","ae","af","bd","be","bf","cd","ce","cf"]

示例 2:

复制代码
输入:digits = ""
输出:[]

示例 3:

复制代码
输入:digits = "2"
输出:["a","b","c"]

📝 核心笔记:电话号码的字母组合 (Letter Combinations)

1. 核心思想 (一句话总结)

"由浅入深,每一层代表一个数字,每一个分支代表一个字母。" 这是一棵标准的 N 叉树 。树的深度等于输入数字的个数 (n),每一层的宽度取决于那个数字对应的字母个数 (3 个或 4 个)。

💡 图像记忆 (密码锁 / 树形扩散):

  • 输入 "23"。
  • 第 0 层 (数字 2):有 3 个选择 'a', 'b', 'c'。我们先选 'a'。
  • 第 1 层 (数字 3):有 3 个选择 'd', 'e', 'f'。我们先选 'd'。
  • 触底:组合成 "ad",加入答案。
  • 回溯:回到第 1 层,改选 'e' -> "ae"...
2. 算法流程 (三步走)
  1. 建立映射 (Mapping) :准备一个 String[] 数组,下标 2~9 对应 "abc"~"wxyz"。
  2. 定长容器 (Optimization)
    • 因为每个数字只贡献 1 个字母,结果长度肯定是 n
    • 直接开一个 char[n] 数组,用 path[i] = c 来填空。
  1. DFS 填空
    • 取出当前 digits[i] 对应的所有字母。
    • 遍历字母,填入 path[i]
    • 递归 dfs(i + 1)
    • 无需显式回溯 :因为下一次循环会直接修改 path[i] 的值,把旧值覆盖掉,逻辑上等同于"撤销"。
🔍 代码回忆清单 (带注释版)
复制代码
// 题目:LC 17. Letter Combinations of a Phone Number
class Solution {
    // 0 和 1 没对应字母,留空占位
    private static final String[] MAPPING = new String[]{"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};

    public List<String> letterCombinations(String digits) {
        int n = digits.length();
        // 1. 特判空串 (否则 dfs 会把空字符串加入 list,变成 [""] 而不是 [])
        if (n == 0) {
            return List.of();
        }

        List<String> ans = new ArrayList<>();
        // 2. 性能优化关键:定长数组
        // 比 StringBuilder 更快,且无需 removeLast
        char[] path = new char[n]; 
        
        dfs(0, ans, path, digits.toCharArray());
        return ans;
    }

    private void dfs(int i, List<String> ans, char[] path, char[] digits) {
        // 3. Base Case: 填满 n 个坑了
        if (i == digits.length) {
            ans.add(new String(path));
            return;
        }
        
        // 4. 获取当前数字对应的字母表
        // digits[i] 是 char (如 '2'),减 '0' 变成 int (2)
        String letters = MAPPING[digits[i] - '0'];
        
        for (char c : letters.toCharArray()) {
            path[i] = c; // 直接覆盖当前层的坑位
            dfs(i + 1, ans, path, digits); // 进下一层
            // 这里不需要 path[i] = 0 或 remove,因为下次循环 c 变了直接覆盖
        }
    }
}
⚡ 快速复习 CheckList (易错点)
  • \] **空字符串特判了吗?**

    • 如果 digits"",循环进不去,path 是空,但递归 i==0 成立,会加入一个空串 ""ans
    • 题目要求返回 [],所以必须在开头 if (n == 0) return List.of();
  • \] **映射表对齐了吗?**

    • 数组下标 0 和 1 通常设为空串。如果你为了省空间把 '2' 映射到下标 0,那代码里的 digits[i] - '0' 就要改成 digits[i] - '2',容易出错。建议直接浪费前两个空间,保持直观。
  • \] **path[i]****为什么不用回溯?**

    • StringBuilder 写法sb.append(c) -> dfs -> sb.deleteCharAt(...)。因为是同一个变长容器追加。
    • char[] 写法path[i] = c。每一层只负责填自己的坑 i,下一轮循环直接覆盖,互不干扰。
🖼️ 数字演练

输入 digits = "23"

  1. DFS(0) : digits[0] = '2', letters="abc"。
    • 'a' -> path[0] = 'a' -> Call DFS(1)
  1. DFS(1) : digits[1] = '3', letters="def"。
    • 'd' -> path[1] = 'd' (path="ad") -> Call DFS(2) -> Add "ad"
    • 回退。
    • 'e' -> path[1] = 'e' (path="ae" 覆盖了 d) -> Call DFS(2) -> Add "ae"
    • ...
  1. Back to DFS(0):
    • 'b' -> path[0] = 'b' (path="be" 覆盖了 a) -> Call DFS(1)...

(最终结果:ad, ae, af, bd, be, bf, cd, ce, cf)

相关推荐
0 0 01 小时前
CCF-CSP 32-2 因子化简(prime)【C++】考点:素数因子分解(试除法)
开发语言·数据结构·c++·算法
yyy(十一月限定版)2 小时前
图论——最短路Dijkstra算法
算法·图论
重生之我是Java开发战士2 小时前
【优选算法】分治:快速排序与归并排序
算法
专注VB编程开发20年2 小时前
早期的redis是进程内的字典列表操作,后面改成TCP网络调用
数据库·redis·算法·缓存
仰泳的熊猫2 小时前
题目1545:蓝桥杯算法提高VIP-现代诗如蚯蚓
数据结构·c++·算法·蓝桥杯
TracyCoder1232 小时前
LeetCode Hot100(57/100)——5. 最长回文子串
算法·leetcode·职场和发展
zh_xuan2 小时前
kotlin 挂起函数
android·开发语言·kotlin
载数而行5202 小时前
复杂度问题
c语言·数据结构·c++·算法·排序算法
WZ188104638692 小时前
LeetCode第20题
算法·leetcode