【Hot 100 回溯篇】一杯奶茶喝懂「回溯算法」!|LeetCode 46. 全排列

77 阅读3分钟

✨ 一篇让小可爱都能看懂的回溯算法讲解
🍹 用“奶茶铺点单”的生活例子彻底理解递归 + 回溯思维!
(带题:LeetCode-Hot100 46《全排列》)


💬 一、回溯法到底是干嘛的?

一句话解释:

回溯法就是“试错 + 撤销”的暴力搜索。

当我们不知道哪条路对的时候,就:
👉 先走一步,
👉 不对就退回来,
👉 再换一条路继续试。

就像在一座迷宫里找出口:

🚶‍♀️ 走 → 撞墙 → 回头 → 换条路 → 继续走

这,就是“回溯”的核心精神。


🍹 二、生活比喻版:奶茶铺点单法

想象你在一家奶茶店打工,顾客说:

“请列出我能点的所有奶茶组合,
每种口味只能用一次,比如珍珠、红豆、椰果。”

于是你开始了“回溯式点单”👇

🧋 Step 1. 先选一杯

顾客可能先选珍珠奶茶。
path = [珍珠]

🧋 Step 2. 再选下一杯

还没点完,再选红豆奶茶。
path = [珍珠, 红豆]

🧋 Step 3. 继续加

选椰果奶茶。
path = [珍珠, 红豆, 椰果]

✅ 菜单选完,保存这单!

🧋 Step 4. 撤销

“好,我再换一种组合看看~”
把最后一个口味“椰果”撤掉。
path = [珍珠, 红豆]

再试别的顺序,比如红豆 → 椰果互换。
如此反复,直到所有组合都试完。


💡 三、题目背景:LeetCode 46. 全排列

给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列
你可以 按任意顺序 返回答案。

示例:

输入:nums = [1,2,3]
输出:[
 [1,2,3],
 [1,3,2],
 [2,1,3],
 [2,3,1],
 [3,1,2],
 [3,2,1]
]

🧋 四、题目翻译成奶茶店语言

你有三种配料:

A = 珍珠
B = 红豆
C = 椰果

顾客说:“请你列出所有调配顺序,比如:

  • 珍珠→红豆→椰果
  • 红豆→椰果→珍珠
  • 椰果→珍珠→红豆 …

这就是「全排列」的本质:

列出所有不重复的加料顺序。


💻 五、代码实现(完整版 + 注释)

var permute = function(nums) {
  const res = [];      // 存放结果的“托盘”
  const path = [];     // 当前正在调配的“杯子”
  const used = Array(nums.length).fill(false); // 哪些料加过

  function backtrack() {
    // 🍶 杯子满了,就打包保存
    if (path.length === nums.length) {
      res.push([...path]);
      return;
    }

    // 🧋 遍历每一种配料
    for (let i = 0; i < nums.length; i++) {
      if (used[i]) continue; // 已加过就跳过

      // 🥤 做选择:加一份配料
      path.push(nums[i]);
      used[i] = true;

      // 🍹 递归:继续往下加下一份料
      backtrack();

      // 🔙 撤销选择:倒掉刚加的料
      path.pop();
      used[i] = false;
    }
  }

  backtrack();   // 从空杯子开始试
  return res;    // 返回所有排列(所有奶茶组合)
};

“开始调奶茶!等全部组合完成后,把托盘端出来给顾客。”


🧠 六、运行过程演示(nums = [1,2,3])

start []
 ├── [1] → 继续选 → [1,2][1,2,3]
 │               ↳ 回退 → [1,3,2]
 ├── [2][2,1,3], [2,3,1]
 └── [3][3,1,2], [3,2,1]

每次深入是递归(往下一层点单)
每次回退是回溯(撤销上一步)


调用层级当前 path操作说明
[]顾客刚来,还没选
[1]选了珍珠
[1,2]接着选红豆
[1,2,3] ✅满杯!保存结果
← 回溯[1,2]拿掉3换别的
[1,3]换椰果
[1,3,2] ✅保存结果
← 回溯[1]撤销,换起手配料
[2]从红豆开始
直到列完所有顺序

🍰 七、记忆法总结(小可爱专属 🤓)

回溯三部曲:
🥤「选择」 → 选一种配料放入杯子
🧭「探索」 → 递归往下继续加
🔙「撤销」 → 拿掉最后加的料,换别的试

一句话总结:
“一边加料一边记录,做完一杯就回去换口味!” 😆


🎯 八、最终成果

console.log(permute([1,2,3]));

输出:

[
 [1,2,3],
 [1,3,2],
 [2,1,3],
 [2,3,1],
 [3,1,2],
 [3,2,1]
]

💬 九、最后总结

🍵 回溯法其实就是「带撤销功能的递归」。
每次做选择 → 尝试 → 回退 → 换新路。

适合场景:

  • 全排列 / 组合 / 子集
  • 数独求解
  • 八皇后问题
  • 迷宫路径

🧋一句话收尾:

“回溯,就是奶茶小哥穷举所有加料顺序,
每次尝完一种,再回去换新口味!” 🍓