LC 17 电话号码的字母组合:我的回溯算法,就是从这道题开窍的

刚接触回溯算法的时候,我总觉得这东西很 "玄学"------ 看着大佬写的代码短短几行,自己动手就处处卡壳。这道电话号码的字母组合,就是我的回溯启蒙题。说出来不怕你笑,我前前后后写了三遍才完全跑通,不是漏了边界就是递归参数写错。

题目说的啥

题意很简单,给你一串只包含 2-9 的数字,每个数字对应手机按键上的几个字母。你要返回所有可能的字母组合,顺序随便。比如输入"23",2 对应 abc,3 对应 def,两两组合下来就是 9 种结果。

一开始的笨想法

最开始我脑子没转过弯,想着不就是排列组合吗,写循环呗。可转念一想,数字长度是不固定的啊,最多有 4 位,难道我要写四层嵌套循环?太蠢了,而且扩展性极差,万一来个 10 位数字我总不能写 10 层吧。这个思路直接被我 pass 了。

回溯是怎么想到的

后来翻了翻算法笔记,才反应过来:这种 "多层循环但层数不固定" 的问题,天生就是回溯的主场。说白了,就是用递归帮我们 "自动" 叠循环层数。

每处理一个数字,就遍历它对应的所有字母,选一个字母加入当前路径,再递归处理下一个数字。等递归回来,再把刚才选的字母拿掉,换下一个,也就是常说的「做选择 - 递归 - 撤销选择」三步。

拿"23"举个例子:先处理第一位数字 2,有 a、b、c 三个选择。选 a 之后,再处理第二位数字 3,有 d、e、f 三个选择,拼出 ad、ae、af。选完一轮就退回去,把 a 拿掉,换 b 开头,再拼出 bd、be、bf,以此类推。其实就是深度优先遍历一棵决策树,走到叶子节点就收集答案。

我最常用的回溯写法

我现在刷回溯题,更喜欢用数组来存当前路径,配合 push 和 pop 做显式回溯。这种写法的模板感特别强,几乎所有组合、排列类题目都能套这个架子,写熟了上手特别快。

javascript 复制代码
var letterCombinations = function (digits) {
    // 空输入直接返回空数组!这个坑我第一次写必踩
    if (digits.length === 0) return []

    // 用二维数组做映射,下标直接对应数字,不用写对象
    const map = [[], [], ['a', 'b', 'c'], ['d', 'e', 'f'], ['g', 'h', 'i'], 
                 ['j', 'k', 'l'], ['m', 'n', 'o'], ['p', 'q', 'r', 's'], 
                 ['t', 'u', 'v'], ['w', 'x', 'y', 'z']]
    const ans = []
    const n = digits.length
    // 用数组维护当前路径,比字符串拼接更有"回溯"那味儿
    const path = []

    // dfs函数:当前处理到第 i 个数字
    var dfs = function (i) {
        // 递归终点:所有数字都处理完了,把路径拼成字符串存起来
        if (i === n) {
            ans.push(path.join(''))
            return
        }
        // 取出当前数字对应的所有字母
        const cur = map[digits[i]]
        // 遍历每个字母,挨个选
        for (const c of cur) {
            // 1. 做选择:把当前字母加入路径
            path.push(c)
            // 2. 递归:处理下一个数字
            dfs(i + 1)
            // 3. 撤销选择:递归回来后,把刚才的字母拿掉,换下一个
            path.pop()
        }
    }

    // 从第 0 个数字开始搜
    dfs(0)
    return ans
}

说两个我踩过的坑:第一个就是空输入的判断。第一次写这种写法的时候我没加,空字符串输入居然返回了[""],直接 WA。别小看这个边界,面试的时候一紧张真的很容易漏掉。第二个就是映射表,7 和 9 是四个字母,我一开始图快写成三个了,测例"7"的时候少了一个结果,找了半天 bug。

复杂度

时间复杂度是 O (3^m * 4^n),m 是对应 3 个字母的数字个数,n 是对应 4 个字母的(也就是 7 和 9)。毕竟每个数字对应的字母都要遍历一遍,组合数就是这么多。空间复杂度主要是递归调用栈和 path 数组,深度就是数字的长度,最多 4 层,其实很小。

还有别的写法吗

除了这种显式回溯,也可以用字符串拼接的隐式写法,每次递归直接传拼接后的新字符串,不用手动 pop。两种思路本质完全一样,只是实现形式不同,我个人更偏爱数组 + pop 的写法,更贴合回溯的核心思想,也不容易乱。当然也能用 BFS 迭代一层层拼结果,不过回溯写起来最顺手,也最通用。

最后唠两句

这道题绝对是回溯算法的入门必刷题,没有复杂的剪枝,也没有烦人的去重,刚好能帮你理解 "递归代替多层循环" 和 "选择 - 撤销" 的核心思想。我自己最大的收获就是,终于摸透了回溯的通用模板 ------ 路径、选择列表、递归终止条件,再加三步选择法,大部分中等回溯题都能直接套。

你第一次接触回溯是刷的哪道题?有没有在递归边界或者撤销选择上栽过跟头?评论区聊聊你的踩坑经历,我每条评论都会看。如果觉得这篇对你有帮助,点个赞让更多小伙伴看到呀~

相关推荐
海上小飞龙24 分钟前
【KMP算法-下篇】同一道题:Java 库函数 2600 微秒,手写 KMP 16 微秒
java·开发语言·算法
青少儿编程课堂41 分钟前
后缀自动机解析:本质不同子串与最长重复片段统计
c++·python·算法·bfs·信息学竞赛
朝朝辞暮i41 分钟前
C++ 第 12 课:局部变量、作用域、变量生命周期
开发语言·c++·算法
乐迪信息2 小时前
港口船舶逆行怎么管?AI防爆摄像机搭载智能检测算法
大数据·人工智能·深度学习·算法·计算机视觉
星栈3 小时前
pnpm 12 升级实测
前端·javascript
Sirens.3 小时前
Java并发锁详解:六类锁策略与 synchronized 底层原理
java·前端·算法
和裕3 小时前
全纸结构重型纸箱能否满足 1 吨以上设备出口熏蒸豁免要求?通关合规性全解析
大数据·运维·网络·人工智能·算法
Ai-_Man3 小时前
您您这可以把Microsofat Copilot的多个会话比如说。左侧的多个会话一次性导出吗?不是单条会话里面的多次会对话。AI导出鸭
javascript·人工智能·ai·小程序·电脑·copilot
tangguofeng4 小时前
不带头结点的链队操作集(不含InitFlag)(C语言版)
算法
苏打豆5 小时前
CRISP源码阅读——基于ROS2力矩反馈控制器与速度加速度零空间
c++·线性代数·算法·矩阵·机器人