【LeetCode 热题 100】17. 电话号码的字母组合——DFS+回溯

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

整体思路

这段代码旨在解决一个经典的组合问题:电话号码的字母组合 (Letter Combinations of a Phone Number)。给定一个只包含数字 2-9 的字符串,它需要返回所有这些数字可能代表的字母组合。

该算法采用的核心方法是 回溯法(Backtracking),通过 深度优先搜索(DFS) 来系统地探索所有可能的字母组合。

算法的整体思路可以分解为以下步骤:

  1. 映射关系与初始化

    • 首先,代码定义了一个静态的 MAPPING 数组,它存储了从数字 0-9 到对应字母的映射关系(0 和 1 不对应任何字母)。
    • 主函数 letterCombinations 处理一些边界情况(如输入为空字符串),并初始化一个结果列表 ans 和一个 StringBuilder path 用于构建当前的字母组合。
  2. 逐位构建组合

    • 算法将生成一个字母组合的过程看作是依次为输入数字字符串中的每个数字选择一个对应的字母。
    • 它定义了一个 dfs(i, ...) 函数,其核心任务是处理第 i 个数字 digits[i],并为其选择一个可能的字母。
  3. 递归与回溯的核心逻辑

    • dfs 函数从第 0 个数字开始 (i=0)。在处理第 i 个数字 cs[i] 时:
      • 它首先根据 MAPPING 找到这个数字对应的所有可能的字母(例如,‘2’ 对应 “abc”)。
      • 然后,它会遍历这些可能的字母。
    • 对于每个可能的字母 c
      • 选择 (Choose):将字母 c 追加到当前正在构建的组合 path 的末尾 (path.append(c))。
      • 探索 (Explore):做出选择后,递归地调用 dfs(i + 1, ...),去解决下一个子问题——为第 i+1 个数字选择字母。
      • 撤销选择 (Unchoose / Backtrack):当对 i+1 的探索(即 dfs(i + 1, ...) 调用)返回后,必须撤销刚才的选择。这是回溯法的精髓。
        • 将刚刚追加的字母 cpath 的末尾删除 (path.deleteCharAt(path.length() - 1))。
        • 这样,在下一次循环中,就可以尝试该数字的其他可能字母,从而形成不同的组合。
  4. 递归终止条件

    • i 的值等于输入数字字符串的长度时(if (i == cs.length)),意味着所有数字都已经被成功地赋予了一个字母。
    • 此时,一个完整的字母组合就构建好了(存储在 path 中)。
    • path 转换为字符串(path.toString())并添加到最终的结果列表 ans 中。

通过这个“选择-探索-撤销”的循环,算法能够不重不漏地遍历所有可能的字母组合。

完整代码

class Solution {
    // 定义数字到字母的静态映射关系
    private final static String[] MAPPING = new String[] { "", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz" };

    /**
     * 计算电话号码对应的所有字母组合。
     * @param digits 只包含数字 2-9 的字符串
     * @return 所有可能的字母组合列表
     */
    public List<String> letterCombinations(String digits) {
        int n = digits.length();
        // 处理空输入的边界情况
        if (n == 0) {
            return List.of(); // 返回一个不可变的空列表
        }
        // ans: 最终的结果列表
        List<String> ans = new ArrayList<>();
        // path: 使用 StringBuilder 来高效地构建和修改当前组合路径
        StringBuilder path = new StringBuilder();
        
        // 从第 0 个数字开始进行深度优先搜索
        dfs(0,  digits.toCharArray(), ans, path);
        return ans;
    }
    
    /**
     * 深度优先搜索(回溯)辅助函数。
     * @param i 当前正在处理的数字的索引
     * @param cs 输入的数字字符数组
     * @param ans 结果列表
     * @param path 当前构建的字母组合
     */
    private void dfs(int i, char[] cs, List<String> ans, StringBuilder path) {
        // 递归终止条件:当处理完所有数字时,一个完整的组合已生成
        if (i == cs.length) {
            // 将当前路径(StringBuilder)转换为字符串并加入结果列表
            ans.add(path.toString());
            return; // 结束当前递归分支
        }
        
        // 获取当前数字 cs[i] 对应的字母字符串
        // cs[i] - '0' 将字符数字转换为整型数字
        String letters = MAPPING[cs[i] - '0'];
        
        // 遍历当前数字所有可能的字母
        for (char c : letters.toCharArray()) {
            // 选择 (Choose): 将字母 c 追加到当前组合的末尾
            path.append(c);
            
            // 探索 (Explore): 递归地去处理下一个数字
            dfs(i + 1, cs, ans, path);
            
            // 撤销选择 (Unchoose / Backtrack): 将刚刚追加的字母删除,
            // 以便恢复到上一层状态,尝试其他字母。
            path.deleteCharAt(path.length() - 1);
        }
    }
}

时空复杂度

时间复杂度:O(4^N * N)

  1. 组合数量
    • N 为输入数字字符串 digits 的长度。
    • 电话号码盘上,大部分数字对应 3 个字母,少数(7和9)对应 4 个字母。
    • 在最坏的情况下(例如,输入全是 “7” 或 “9”),每个数字都有 4 个选择。因此,总的组合数量级为 O(4^N)
  2. 构建每个组合的成本
    • 算法的搜索过程可以看作是在一棵深度为 N 的“组合树”上进行DFS。这棵树有 O(4^N) 个叶子节点,每个叶子节点代表一个完整的组合。
    • 当到达一个叶子节点时(即 i == cs.length),需要执行 path.toString() 操作。StringBuilder 转换为 String 需要复制其内部的字符数组,其长度为 N。这是一个 O(N) 的操作。
  3. 综合分析
    • 总的时间复杂度约等于 (组合的数量 * 每个组合的生成成本)。
    • O(4^N) * O(N)
    • 因此,总时间复杂度为 O(N * 4^N)

空间复杂度:O(N)

  1. 主要存储开销:我们分析的是额外辅助空间,不包括存储最终结果的 ans 列表(否则空间将是 O(N * 4^N))。
    • StringBuilder path: 用于存储当前路径。其最大长度为 N。空间复杂度为 O(N)
    • 递归调用栈dfs 函数的最大递归深度为 N。因此,递归栈所占用的空间为 O(N)
    • MAPPING 数组是静态常量,其空间为 O(1)。

综合分析
算法所需的额外辅助空间由 path 和递归栈深度共同决定。它们都是 O(N) 级别的。因此,总的辅助空间复杂度为 O(N)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xumistore

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值