图解题库 / 回溯

46. 全排列

中等 含交互动画

学完这道你应该能: 说清它为什么用「回溯」,而不是死记步骤; 合上页面,自己默写出核心代码; 用 30 秒把思路讲清楚。

题目描述

给一组不重复的数,返回它们的所有排列

nums = [1, 2, 3]
输出 = 6 种排列

思路解析

想象你在一层层做决定:每层挑一个还没用过的数放进路径 path;path 凑满 3 个就记下来;然后撤销刚才的选择,回到上一层换一个数继续。

开始:路径 path 一开始是空的,三个数都没用过,结果 res 也是空。

第1层 · 选 1:第一层先选 1:标记 1 已用,放进 path。path = [1]。

第2层 · 选 2:进入第二层,在剩下的 2、3 里选 2。path = [1, 2]。

第3层 · 选 3:第三层只剩 3,选它。path = [1, 2, 3],凑满 3 个了!

收集:path 长度等于 3,记下这个排列 [1,2,3]。然后开始往回退(回溯)。

撤销 3:撤销刚才的 3:把它移出 path、标记为没用过。回到第二层,可这层除了 3 没别的可选了,继续往上退。

撤销 2:撤销 2,回到 path = [1]。第一层选过 1 后,第二层还能换成 3 试试。

第2层 · 改选 3:这次第二层选 3。path = [1, 3]。

第3层 · 选 2 · 收集:第三层只剩 2,凑满 [1, 3, 2],记下来。以 1 开头的排列全找完了。

一路撤销回开头:继续撤销,一直退回 path 为空。现在换第一个数,不选 1 了。

换开头 · 选 2:第一层改选 2。下面用同样的「选择→递归→撤销」走一遍,你会看到流程和以 1 开头时一模一样。

第2层 · 选 1:剩下 1、3,先选 1。path = [2, 1]。

第3层 · 选 3 · 收集:只剩 3,凑满 [2, 1, 3],记下来。

撤销回 [2]:撤销 3、再撤销 1,回到 path = [2]。第二层换成 3 再试。

第2层 · 改选 3:第二层选 3。path = [2, 3]。

第3层 · 选 1 · 收集:只剩 1,凑满 [2, 3, 1]。以 2 开头的两种也找齐了。

撤销回开头 · 选 3:一路撤销回空,第一层最后选 3。同样的流程会排出 [3,1,2]、[3,2,1]。

全部找齐:6 种排列不重不漏全部找齐。这就是回溯的威力:系统地穷举所有可能。

子集、组合、组合总和、N 皇后……几乎所有「列出所有可能」的题,都是这套回溯三件套的变体。

参考代码(Python)

Python
1def backtrack(path):
2    if len(path) == len(nums):     # 选满了
3        res.append(path[:])        # 收集一份副本
4        return
5    for i in range(len(nums)):
6        if used[i]: continue       # 跳过用过的
7        used[i] = True             # 做选择
8        path.append(nums[i])
9        backtrack(path)            # 往下走
10        path.pop()                 # 撤销选择
11        used[i] = False

复杂度分析

  • 时间复杂度:O(n × n!) —— n! 种排列,每种花 O(n) 拷贝
  • 空间复杂度:O(n) —— 递归深度 + path

套路模板

选择 → 递归 → 撤销,三件套。子集/组合/排列只改「结束条件」和「选择列表」。

Python
1def backtrack(path):
2    if 满足结束条件:
3        res.append(path[:])   # 注意 [:] 拷贝
4        return
5    for 选择 in 选择列表:
6        if 不能选: continue
7        做选择(path)          # append + used=True
8        backtrack(path)
9        撤销选择(path)        # pop + used=False

易错点

  • res.append(path) → ✅ res.append(path[:])(不拷贝,path 之后被改空,结果会全变空)
  • 做了选择忘了撤销 → ✅ append/pop、used 标记/取消 必须成对(否则别的分支用不到这个数)

以上文字与上方动画完全同一思路:先看动画建立直觉 → 读文字巩固 → 关掉页面自己默写一遍代码,能写出来才算真的掌握

下一题 →200. 岛屿数量 ← 返回题库