17. 电话号码的字母组合

0 阅读3分钟

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

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

示例 1:

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

示例 2:

输入: digits = "2"
输出: ["a","b","c"]

提示:

  • 1 <= digits.length <= 4
  • digits[i] 是范围 ['2', '9'] 的一个数字。

1. 生活案例:老式手机发短信

想象你在用 2000 年左右那种带数字按键的诺基亚手机:

  • 场景:你想输入单词,按下数字键 23

  • 规则

    • 按键 2 对应 a, b, c 三个字母。
    • 按键 3 对应 d, e, f 三个字母。
  • 任务:手机需要列出所有可能的字母组合(比如 ad, ae, af, bd ...),供你选择。

  • 过程

    1. 你先从第一个按键 2 里选一个字母(比如 a)。
    2. 带着这个 a,去第二个按键 3 里挑一个字母。
    3. 如果所有按键都挑完了,这就形成了一个组合。
    4. 然后你退回一步,把 3 号键刚才选的换成下一个字母,继续尝试。

2. 代码实现与详细注释

这是你图片中的代码,我为你加上了详细的“拨号”逻辑注释:

JavaScript

/**
 * @param {string} digits - 输入的数字字符串,如 "23"
 * @return {string[]}
 */
var letterCombinations = function(digits) {
    // 1. 如果没按键,直接返回空数组
    if(digits.length === 0) return [];

    // 2. 建立【按键映射表】:每个数字对应哪些字母
    let map = {
        '2': 'abc', '3': 'def', '4': 'ghi', '5': 'jkl',
        '6': 'mno', '7': 'pqrs', '8': 'tuv', '9': 'wxyz'
    };

    let res = []; // 【仓库】:存放所有组合结果

    /**
     * @param {number} index - 当前正在处理第几个按键
     * @param {string} curStr - 当前已经拼好的字母串
     */
    let backtrack = (index, curStr) => {
        // 【出口】:如果拼好的长度等于按键个数,说明这组拼完了
        if(index === digits.length) {
            res.push(curStr); // 存入结果
            return;
        }

        // 找到当前数字对应的所有字母,例如数字 '2' 对应 "abc"
        let letters = map[digits[index]];

        // 遍历这些字母,分别往下走
        for(let ch of letters) {
            // 【递归】:带着新字母,去处理下一个按键 (index + 1)
            backtrack(index + 1, curStr + ch);
        }
    }

    // 从第 0 个数字、空字符串开始
    backtrack(0, '');
    return res;
};

3. 核心原理解析

这为什么是回溯?

虽然代码里没有显式的 path.pop(),但回溯的思想体现在参数传递中:

  • backtrack 处理完 ch = 'a' 对应的所有分支并返回时,它会自动回到 for 循环。
  • 下一次循环会使用 ch = 'b',这本质上就是撤销了 'a',尝试了 'b'

决策树的深度与宽度

  • 树的深度:由输入 digits 的长度决定。按了几个键,树就有几层。
  • 树的宽度:由按键对应的字母数量决定(通常是 3 或 4 个分叉)。

复杂度分析

  • 时间复杂度O(3N×4M)O(3^N \times 4^M)。其中 NN 是对应 3 个字母的数字个数,MM 是对应 4 个字母的数字个数。这是典型的指数级增长,因为每多按一个键,组合数就会翻几倍。
  • 空间复杂度O(N+M)O(N + M)。主要是递归调用产生的栈深度。