算法:回溯法 - 在解空间树上做 DFS

回溯法:在解空间树上做 DFS

开篇

面试官给你一道全排列,你写出来了。他追一句"如果数组里有重复数字呢?"你愣了三秒,开始在代码里疯狂加 if,越加越乱,最后连不重复的版本也改坏了。

这个场景太常见了。很多人学回溯,学的是"背递归模板"------模板背下来了,但凡题目条件一变就不知道怎么改。根本原因是没有建立起一个统一的心智模型:回溯到底在做什么?为什么同一套框架能解组合、排列、子集、棋盘等完全不同的题?

本文的核心线索:回溯不是一种"算法",是一种系统化穷举的方法论。 它的本质是在一棵"解空间树"上做深度优先搜索。理解了这棵树长什么样,所有回溯题都是同一道题------区别只在树的形状和怎么避免重复。

本文三个重点:

  1. 解空间树怎么画
  2. 全排列为什么有多种写法、它们之间是什么关系
  3. 去重到底在去什么
维度 说明
整体难度 白银+黄金
适合人群 知道递归但一遇到排列组合就懵的人
预估阅读 12-15 分钟
本文重点 解空间树心智模型、全排列三种写法的统一理解、去重的本质

#mermaid-svg-VH6LDyt77100Qbwx{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-VH6LDyt77100Qbwx .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-VH6LDyt77100Qbwx .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-VH6LDyt77100Qbwx .error-icon{fill:#552222;}#mermaid-svg-VH6LDyt77100Qbwx .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-VH6LDyt77100Qbwx .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-VH6LDyt77100Qbwx .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-VH6LDyt77100Qbwx .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-VH6LDyt77100Qbwx .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-VH6LDyt77100Qbwx .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-VH6LDyt77100Qbwx .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-VH6LDyt77100Qbwx .marker{fill:#333333;stroke:#333333;}#mermaid-svg-VH6LDyt77100Qbwx .marker.cross{stroke:#333333;}#mermaid-svg-VH6LDyt77100Qbwx svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-VH6LDyt77100Qbwx p{margin:0;}#mermaid-svg-VH6LDyt77100Qbwx .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-VH6LDyt77100Qbwx .cluster-label text{fill:#333;}#mermaid-svg-VH6LDyt77100Qbwx .cluster-label span{color:#333;}#mermaid-svg-VH6LDyt77100Qbwx .cluster-label span p{background-color:transparent;}#mermaid-svg-VH6LDyt77100Qbwx .label text,#mermaid-svg-VH6LDyt77100Qbwx span{fill:#333;color:#333;}#mermaid-svg-VH6LDyt77100Qbwx .node rect,#mermaid-svg-VH6LDyt77100Qbwx .node circle,#mermaid-svg-VH6LDyt77100Qbwx .node ellipse,#mermaid-svg-VH6LDyt77100Qbwx .node polygon,#mermaid-svg-VH6LDyt77100Qbwx .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-VH6LDyt77100Qbwx .rough-node .label text,#mermaid-svg-VH6LDyt77100Qbwx .node .label text,#mermaid-svg-VH6LDyt77100Qbwx .image-shape .label,#mermaid-svg-VH6LDyt77100Qbwx .icon-shape .label{text-anchor:middle;}#mermaid-svg-VH6LDyt77100Qbwx .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-VH6LDyt77100Qbwx .rough-node .label,#mermaid-svg-VH6LDyt77100Qbwx .node .label,#mermaid-svg-VH6LDyt77100Qbwx .image-shape .label,#mermaid-svg-VH6LDyt77100Qbwx .icon-shape .label{text-align:center;}#mermaid-svg-VH6LDyt77100Qbwx .node.clickable{cursor:pointer;}#mermaid-svg-VH6LDyt77100Qbwx .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-VH6LDyt77100Qbwx .arrowheadPath{fill:#333333;}#mermaid-svg-VH6LDyt77100Qbwx .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-VH6LDyt77100Qbwx .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-VH6LDyt77100Qbwx .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-VH6LDyt77100Qbwx .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-VH6LDyt77100Qbwx .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-VH6LDyt77100Qbwx .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-VH6LDyt77100Qbwx .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-VH6LDyt77100Qbwx .cluster text{fill:#333;}#mermaid-svg-VH6LDyt77100Qbwx .cluster span{color:#333;}#mermaid-svg-VH6LDyt77100Qbwx div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-VH6LDyt77100Qbwx .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-VH6LDyt77100Qbwx rect.text{fill:none;stroke-width:0;}#mermaid-svg-VH6LDyt77100Qbwx .icon-shape,#mermaid-svg-VH6LDyt77100Qbwx .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-VH6LDyt77100Qbwx .icon-shape p,#mermaid-svg-VH6LDyt77100Qbwx .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-VH6LDyt77100Qbwx .icon-shape .label rect,#mermaid-svg-VH6LDyt77100Qbwx .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-VH6LDyt77100Qbwx .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-VH6LDyt77100Qbwx .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-VH6LDyt77100Qbwx :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 回溯法
本质: DFS 遍历解空间树
两类解空间树
全排列三种写法
去重
五类高频题型
面试实战
解空间 = 所有候选答案的树形结构
三要素: 路径 / 选择列表 / 结束条件
子集树 (选或不选) → 2^n
排列树 (选哪个) → n!
写法一: 为坑找元素 + used 数组
写法二: 为元素找坑 + 状态压缩
写法三: 交换法 (极致压缩)
树层去重 vs 树枝去重
排序 + usedi-1 判定
不能排序时: HashSet 去重
组合 / 排列 / 子集
棋盘搜索 / 切割分割

一、回溯的本质:在解空间树上做 DFS

1.1 什么是解空间树

[1, 2, 3] 的全排列来说。第一个位置可以放 1、2、3 三个数中的任何一个;第一个位置确定后,第二个位置从剩下的两个数里选;第三个位置没得选,只剩一个。把所有的选择过程画出来,就是一棵树:
#mermaid-svg-Rc5mxQJpHymBvoPg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Rc5mxQJpHymBvoPg .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Rc5mxQJpHymBvoPg .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Rc5mxQJpHymBvoPg .error-icon{fill:#552222;}#mermaid-svg-Rc5mxQJpHymBvoPg .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Rc5mxQJpHymBvoPg .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Rc5mxQJpHymBvoPg .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Rc5mxQJpHymBvoPg .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Rc5mxQJpHymBvoPg .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Rc5mxQJpHymBvoPg .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Rc5mxQJpHymBvoPg .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Rc5mxQJpHymBvoPg .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Rc5mxQJpHymBvoPg .marker.cross{stroke:#333333;}#mermaid-svg-Rc5mxQJpHymBvoPg svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Rc5mxQJpHymBvoPg p{margin:0;}#mermaid-svg-Rc5mxQJpHymBvoPg .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Rc5mxQJpHymBvoPg .cluster-label text{fill:#333;}#mermaid-svg-Rc5mxQJpHymBvoPg .cluster-label span{color:#333;}#mermaid-svg-Rc5mxQJpHymBvoPg .cluster-label span p{background-color:transparent;}#mermaid-svg-Rc5mxQJpHymBvoPg .label text,#mermaid-svg-Rc5mxQJpHymBvoPg span{fill:#333;color:#333;}#mermaid-svg-Rc5mxQJpHymBvoPg .node rect,#mermaid-svg-Rc5mxQJpHymBvoPg .node circle,#mermaid-svg-Rc5mxQJpHymBvoPg .node ellipse,#mermaid-svg-Rc5mxQJpHymBvoPg .node polygon,#mermaid-svg-Rc5mxQJpHymBvoPg .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Rc5mxQJpHymBvoPg .rough-node .label text,#mermaid-svg-Rc5mxQJpHymBvoPg .node .label text,#mermaid-svg-Rc5mxQJpHymBvoPg .image-shape .label,#mermaid-svg-Rc5mxQJpHymBvoPg .icon-shape .label{text-anchor:middle;}#mermaid-svg-Rc5mxQJpHymBvoPg .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Rc5mxQJpHymBvoPg .rough-node .label,#mermaid-svg-Rc5mxQJpHymBvoPg .node .label,#mermaid-svg-Rc5mxQJpHymBvoPg .image-shape .label,#mermaid-svg-Rc5mxQJpHymBvoPg .icon-shape .label{text-align:center;}#mermaid-svg-Rc5mxQJpHymBvoPg .node.clickable{cursor:pointer;}#mermaid-svg-Rc5mxQJpHymBvoPg .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Rc5mxQJpHymBvoPg .arrowheadPath{fill:#333333;}#mermaid-svg-Rc5mxQJpHymBvoPg .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Rc5mxQJpHymBvoPg .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Rc5mxQJpHymBvoPg .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Rc5mxQJpHymBvoPg .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Rc5mxQJpHymBvoPg .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Rc5mxQJpHymBvoPg .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Rc5mxQJpHymBvoPg .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Rc5mxQJpHymBvoPg .cluster text{fill:#333;}#mermaid-svg-Rc5mxQJpHymBvoPg .cluster span{color:#333;}#mermaid-svg-Rc5mxQJpHymBvoPg div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Rc5mxQJpHymBvoPg .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Rc5mxQJpHymBvoPg rect.text{fill:none;stroke-width:0;}#mermaid-svg-Rc5mxQJpHymBvoPg .icon-shape,#mermaid-svg-Rc5mxQJpHymBvoPg .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Rc5mxQJpHymBvoPg .icon-shape p,#mermaid-svg-Rc5mxQJpHymBvoPg .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Rc5mxQJpHymBvoPg .icon-shape .label rect,#mermaid-svg-Rc5mxQJpHymBvoPg .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Rc5mxQJpHymBvoPg .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Rc5mxQJpHymBvoPg .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Rc5mxQJpHymBvoPg :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 1
2
3
3
2
2
1
3
3
1
3
1
2
2
1

这棵树的每一条从根到叶子的路径,就是一个候选解------比如最左边那条路径是 [1, 2, 3]。树的全部叶子节点,就是全部 3! = 6 种排列。

这棵树叫做解空间树。所有回溯问题,本质上都是在某棵解空间树上做深度优先遍历:沿着一条路径往下走,走到底就收集答案或者判定不可行;然后退回上一步(这就是"回溯"),换一条分支继续走。

1.2 三要素

无论题目怎么变,回溯框架永远围绕三样东西:

  • 路径 :从根到当前节点,已经做出的选择序列。比如在上面的树中,走到第二层左边节点时,路径是 [1, 2]
  • 选择列表 :当前节点还可以做的选择。走到 [1, 2] 这个节点时,选择列表里只剩 [3]
  • 结束条件:什么时候停。通常是到达叶节点------路径长度等于 n,或者选择列表为空。

回溯的过程就是:在每个节点,从选择列表中拿一个选择加入路径 → 递归进入下一层 → 递归返回后把这个选择从路径中撤销(回溯)→ 尝试选择列表中的下一个。

用伪代码表达这个通用框架:

cpp 复制代码
// 某一个 STATE,在 CHOOSE 的作用下,可以切换到一个新的 STATE
// 有多少种 CHOICE,就会有多少种新的 STATE
// 退出条件:选择列表为空,或找到一组解
vector<PATH> result;
vector<CHOICE> path;
bool<STATE> visited;

void dfs(STATE cur_state) {
    if (is_solution(cur_state)) {
        result.push_back(path);
        return;
    }

    vector<CHOICE> choices = get_all_choices_by_state(cur_state);
    for (each choice : choices) {
        STATE new_state = trans_to_new_state_by_choice(cur_state, choice);
        if (visited(new_state)) continue;
        path.append(choice);
        visited[new_state] = true;
        dfs(new_state);
        visited[new_state] = false;
        path.remove(choice);
    }
}

这个框架是所有回溯题的骨架。不同题目的区别仅在于:什么是"状态"、什么是"选择列表"、什么是"结束条件"、什么是"不合法"。
#mermaid-svg-rc1lYYZc1AYpR3KD{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-rc1lYYZc1AYpR3KD .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-rc1lYYZc1AYpR3KD .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-rc1lYYZc1AYpR3KD .error-icon{fill:#552222;}#mermaid-svg-rc1lYYZc1AYpR3KD .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-rc1lYYZc1AYpR3KD .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-rc1lYYZc1AYpR3KD .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-rc1lYYZc1AYpR3KD .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-rc1lYYZc1AYpR3KD .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-rc1lYYZc1AYpR3KD .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-rc1lYYZc1AYpR3KD .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-rc1lYYZc1AYpR3KD .marker{fill:#333333;stroke:#333333;}#mermaid-svg-rc1lYYZc1AYpR3KD .marker.cross{stroke:#333333;}#mermaid-svg-rc1lYYZc1AYpR3KD svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-rc1lYYZc1AYpR3KD p{margin:0;}#mermaid-svg-rc1lYYZc1AYpR3KD .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-rc1lYYZc1AYpR3KD .cluster-label text{fill:#333;}#mermaid-svg-rc1lYYZc1AYpR3KD .cluster-label span{color:#333;}#mermaid-svg-rc1lYYZc1AYpR3KD .cluster-label span p{background-color:transparent;}#mermaid-svg-rc1lYYZc1AYpR3KD .label text,#mermaid-svg-rc1lYYZc1AYpR3KD span{fill:#333;color:#333;}#mermaid-svg-rc1lYYZc1AYpR3KD .node rect,#mermaid-svg-rc1lYYZc1AYpR3KD .node circle,#mermaid-svg-rc1lYYZc1AYpR3KD .node ellipse,#mermaid-svg-rc1lYYZc1AYpR3KD .node polygon,#mermaid-svg-rc1lYYZc1AYpR3KD .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-rc1lYYZc1AYpR3KD .rough-node .label text,#mermaid-svg-rc1lYYZc1AYpR3KD .node .label text,#mermaid-svg-rc1lYYZc1AYpR3KD .image-shape .label,#mermaid-svg-rc1lYYZc1AYpR3KD .icon-shape .label{text-anchor:middle;}#mermaid-svg-rc1lYYZc1AYpR3KD .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-rc1lYYZc1AYpR3KD .rough-node .label,#mermaid-svg-rc1lYYZc1AYpR3KD .node .label,#mermaid-svg-rc1lYYZc1AYpR3KD .image-shape .label,#mermaid-svg-rc1lYYZc1AYpR3KD .icon-shape .label{text-align:center;}#mermaid-svg-rc1lYYZc1AYpR3KD .node.clickable{cursor:pointer;}#mermaid-svg-rc1lYYZc1AYpR3KD .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-rc1lYYZc1AYpR3KD .arrowheadPath{fill:#333333;}#mermaid-svg-rc1lYYZc1AYpR3KD .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-rc1lYYZc1AYpR3KD .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-rc1lYYZc1AYpR3KD .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-rc1lYYZc1AYpR3KD .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-rc1lYYZc1AYpR3KD .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-rc1lYYZc1AYpR3KD .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-rc1lYYZc1AYpR3KD .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-rc1lYYZc1AYpR3KD .cluster text{fill:#333;}#mermaid-svg-rc1lYYZc1AYpR3KD .cluster span{color:#333;}#mermaid-svg-rc1lYYZc1AYpR3KD div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-rc1lYYZc1AYpR3KD .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-rc1lYYZc1AYpR3KD rect.text{fill:none;stroke-width:0;}#mermaid-svg-rc1lYYZc1AYpR3KD .icon-shape,#mermaid-svg-rc1lYYZc1AYpR3KD .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-rc1lYYZc1AYpR3KD .icon-shape p,#mermaid-svg-rc1lYYZc1AYpR3KD .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-rc1lYYZc1AYpR3KD .icon-shape .label rect,#mermaid-svg-rc1lYYZc1AYpR3KD .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-rc1lYYZc1AYpR3KD .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-rc1lYYZc1AYpR3KD .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-rc1lYYZc1AYpR3KD :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是

不合法
合法
开始 dfs(state)
到达结束条件?

路径长度 = n / 无剩余选择
收集当前路径

加入结果集
返回
遍历选择列表

每个 choice
choice 合法?
跳过该 choice
做出选择

path += choice

visited = true
递归 dfs(next_state)
撤销选择

path -= choice

visited = false

1.3 与 1.4.0 的呼应

在 1.4.0 中我们讲过,所有算法都在做同一件事------在解空间中搜索。回溯在五大策略中的定位是**"利用约束条件来剪枝的穷举"**。它不像贪心那样只看一步,也不像 DP 那样记表避免重复计算。回溯的思路最朴素:我把所有可能都试一遍,但在试的过程中,一旦发现当前路径已经违反约束,就立刻回头------不把这条死路走到底。

贪心不成立、DP 状态定义不出来的时候,回溯是保底方案。它一定能给出正确答案,代价是搜索量可能很大。但在实际面试中,回溯类题目的数据规模通常不大(n ≤ 20 甚至更小),搜索量完全可以接受。

二、两类解空间树:子集树 vs 排列树

不同的问题对应不同形状的解空间树。绝大多数回溯题的解空间树,属于以下两类之一。
#mermaid-svg-YUbbOBUvQSICnMCM{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-YUbbOBUvQSICnMCM .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-YUbbOBUvQSICnMCM .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-YUbbOBUvQSICnMCM .error-icon{fill:#552222;}#mermaid-svg-YUbbOBUvQSICnMCM .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-YUbbOBUvQSICnMCM .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-YUbbOBUvQSICnMCM .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-YUbbOBUvQSICnMCM .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-YUbbOBUvQSICnMCM .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-YUbbOBUvQSICnMCM .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-YUbbOBUvQSICnMCM .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-YUbbOBUvQSICnMCM .marker{fill:#333333;stroke:#333333;}#mermaid-svg-YUbbOBUvQSICnMCM .marker.cross{stroke:#333333;}#mermaid-svg-YUbbOBUvQSICnMCM svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-YUbbOBUvQSICnMCM p{margin:0;}#mermaid-svg-YUbbOBUvQSICnMCM .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-YUbbOBUvQSICnMCM .cluster-label text{fill:#333;}#mermaid-svg-YUbbOBUvQSICnMCM .cluster-label span{color:#333;}#mermaid-svg-YUbbOBUvQSICnMCM .cluster-label span p{background-color:transparent;}#mermaid-svg-YUbbOBUvQSICnMCM .label text,#mermaid-svg-YUbbOBUvQSICnMCM span{fill:#333;color:#333;}#mermaid-svg-YUbbOBUvQSICnMCM .node rect,#mermaid-svg-YUbbOBUvQSICnMCM .node circle,#mermaid-svg-YUbbOBUvQSICnMCM .node ellipse,#mermaid-svg-YUbbOBUvQSICnMCM .node polygon,#mermaid-svg-YUbbOBUvQSICnMCM .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-YUbbOBUvQSICnMCM .rough-node .label text,#mermaid-svg-YUbbOBUvQSICnMCM .node .label text,#mermaid-svg-YUbbOBUvQSICnMCM .image-shape .label,#mermaid-svg-YUbbOBUvQSICnMCM .icon-shape .label{text-anchor:middle;}#mermaid-svg-YUbbOBUvQSICnMCM .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-YUbbOBUvQSICnMCM .rough-node .label,#mermaid-svg-YUbbOBUvQSICnMCM .node .label,#mermaid-svg-YUbbOBUvQSICnMCM .image-shape .label,#mermaid-svg-YUbbOBUvQSICnMCM .icon-shape .label{text-align:center;}#mermaid-svg-YUbbOBUvQSICnMCM .node.clickable{cursor:pointer;}#mermaid-svg-YUbbOBUvQSICnMCM .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-YUbbOBUvQSICnMCM .arrowheadPath{fill:#333333;}#mermaid-svg-YUbbOBUvQSICnMCM .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-YUbbOBUvQSICnMCM .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-YUbbOBUvQSICnMCM .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-YUbbOBUvQSICnMCM .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-YUbbOBUvQSICnMCM .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-YUbbOBUvQSICnMCM .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-YUbbOBUvQSICnMCM .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-YUbbOBUvQSICnMCM .cluster text{fill:#333;}#mermaid-svg-YUbbOBUvQSICnMCM .cluster span{color:#333;}#mermaid-svg-YUbbOBUvQSICnMCM div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-YUbbOBUvQSICnMCM .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-YUbbOBUvQSICnMCM rect.text{fill:none;stroke-width:0;}#mermaid-svg-YUbbOBUvQSICnMCM .icon-shape,#mermaid-svg-YUbbOBUvQSICnMCM .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-YUbbOBUvQSICnMCM .icon-shape p,#mermaid-svg-YUbbOBUvQSICnMCM .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-YUbbOBUvQSICnMCM .icon-shape .label rect,#mermaid-svg-YUbbOBUvQSICnMCM .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-YUbbOBUvQSICnMCM .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-YUbbOBUvQSICnMCM .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-YUbbOBUvQSICnMCM :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 排列树 n!
每个位置: 选哪个
位置 1

有 n 个选择
位置 2

有 n-1 个选择
位置 3

有 n-2 个选择
位置 n

只剩 1 个选择
子集树 2^n
每个元素: 选 / 不选
元素 1

元素 2
不选

...
不选

2.1 子集树:每个元素"选或不选"

如果问题的本质是"对 n 个元素各自做一个二元决策------选还是不选",那解空间树就是一棵二叉树,深度为 n,叶节点共 2^n 个。

典型场景:

  • 组合问题:从 n 个数中选 k 个
  • 子集问题:求集合的所有子集
  • 01 背包:每个物品选或不选

{1, 2, 3} 的所有子集为例,解空间树长这样:每一层对应一个元素,左分支代表"选",右分支代表"不选"。8 个叶节点对应 2³ = 8 个子集。

2.2 排列树:每个位置"选哪个"

如果问题的本质是"有 n 个位置,每个位置从剩余候选中选一个来填",那解空间树是一棵多叉树。第一层有 n 个分支,第二层 n-1 个,第三层 n-2 个......叶节点共 n! 个。

典型场景:

  • 全排列:n 个数的所有排列
  • N 皇后:每一行选哪一列放皇后

这就是我们在 1.1 中画的那棵树。

2.3 同一道题不同建模 → 不同搜索量

一道题并不是只有一种建模方式。以 N 皇后为例:

  • 排列树建模(推荐):n 行 n 列棋盘,每行必须放且只放一个皇后。递归第 u 层 = 为第 u 行选择放在哪一列。搜索空间上界是 n!。
  • 子集树建模:把 n² 个格子看作 n² 个元素,每个格子"放或不放"。搜索空间上界是 2^(n²)。

n = 8 时,8! = 40320,而 2^64 ≈ 1.8×10¹⁹。差距是天文数字。选对建模方式,搜索量可以差好几个数量级。
#mermaid-svg-1rirO7wA8AMN0Sgk{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-1rirO7wA8AMN0Sgk .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-1rirO7wA8AMN0Sgk .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-1rirO7wA8AMN0Sgk .error-icon{fill:#552222;}#mermaid-svg-1rirO7wA8AMN0Sgk .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-1rirO7wA8AMN0Sgk .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-1rirO7wA8AMN0Sgk .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-1rirO7wA8AMN0Sgk .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-1rirO7wA8AMN0Sgk .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-1rirO7wA8AMN0Sgk .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-1rirO7wA8AMN0Sgk .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-1rirO7wA8AMN0Sgk .marker{fill:#333333;stroke:#333333;}#mermaid-svg-1rirO7wA8AMN0Sgk .marker.cross{stroke:#333333;}#mermaid-svg-1rirO7wA8AMN0Sgk svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-1rirO7wA8AMN0Sgk p{margin:0;}#mermaid-svg-1rirO7wA8AMN0Sgk .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-1rirO7wA8AMN0Sgk .cluster-label text{fill:#333;}#mermaid-svg-1rirO7wA8AMN0Sgk .cluster-label span{color:#333;}#mermaid-svg-1rirO7wA8AMN0Sgk .cluster-label span p{background-color:transparent;}#mermaid-svg-1rirO7wA8AMN0Sgk .label text,#mermaid-svg-1rirO7wA8AMN0Sgk span{fill:#333;color:#333;}#mermaid-svg-1rirO7wA8AMN0Sgk .node rect,#mermaid-svg-1rirO7wA8AMN0Sgk .node circle,#mermaid-svg-1rirO7wA8AMN0Sgk .node ellipse,#mermaid-svg-1rirO7wA8AMN0Sgk .node polygon,#mermaid-svg-1rirO7wA8AMN0Sgk .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-1rirO7wA8AMN0Sgk .rough-node .label text,#mermaid-svg-1rirO7wA8AMN0Sgk .node .label text,#mermaid-svg-1rirO7wA8AMN0Sgk .image-shape .label,#mermaid-svg-1rirO7wA8AMN0Sgk .icon-shape .label{text-anchor:middle;}#mermaid-svg-1rirO7wA8AMN0Sgk .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-1rirO7wA8AMN0Sgk .rough-node .label,#mermaid-svg-1rirO7wA8AMN0Sgk .node .label,#mermaid-svg-1rirO7wA8AMN0Sgk .image-shape .label,#mermaid-svg-1rirO7wA8AMN0Sgk .icon-shape .label{text-align:center;}#mermaid-svg-1rirO7wA8AMN0Sgk .node.clickable{cursor:pointer;}#mermaid-svg-1rirO7wA8AMN0Sgk .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-1rirO7wA8AMN0Sgk .arrowheadPath{fill:#333333;}#mermaid-svg-1rirO7wA8AMN0Sgk .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-1rirO7wA8AMN0Sgk .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-1rirO7wA8AMN0Sgk .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-1rirO7wA8AMN0Sgk .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-1rirO7wA8AMN0Sgk .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-1rirO7wA8AMN0Sgk .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-1rirO7wA8AMN0Sgk .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-1rirO7wA8AMN0Sgk .cluster text{fill:#333;}#mermaid-svg-1rirO7wA8AMN0Sgk .cluster span{color:#333;}#mermaid-svg-1rirO7wA8AMN0Sgk div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-1rirO7wA8AMN0Sgk .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-1rirO7wA8AMN0Sgk rect.text{fill:none;stroke-width:0;}#mermaid-svg-1rirO7wA8AMN0Sgk .icon-shape,#mermaid-svg-1rirO7wA8AMN0Sgk .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-1rirO7wA8AMN0Sgk .icon-shape p,#mermaid-svg-1rirO7wA8AMN0Sgk .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-1rirO7wA8AMN0Sgk .icon-shape .label rect,#mermaid-svg-1rirO7wA8AMN0Sgk .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-1rirO7wA8AMN0Sgk .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-1rirO7wA8AMN0Sgk .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-1rirO7wA8AMN0Sgk :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 子集树建模 ❌
排列树建模 ✅
快 4.5 亿倍
n 行, 每行选 1 列

搜索空间: n!
n=8: 8! = 40320
n² 个格子, 每个放或不放

搜索空间: 2^(n²)
n=8: 2^64 ≈ 1.8×10¹⁹

面试启示:拿到一道回溯题,第一件事不是写递归,而是想清楚"解空间树长什么样"。选子集树还是排列树,直接决定搜索效率。

三、全排列的三种写法:本质是两种视角

全排列是回溯的标杆题。它有三种经典写法,很多人只会其中一种,遇到变体就慌。这一节把三种写法的关系彻底讲清楚。

3.1 两种基本视角

同一棵排列树,可以从两个角度来遍历:

  • 为坑找元素:我有 n 个坑位(位置 0 到 n-1),递归第 u 层的任务是"为第 u 个坑选一个元素填进去"。每个坑从所有还没被选走的元素中挑一个。
  • 为元素找坑:我有 n 个元素(元素 0 到 n-1),递归第 u 层的任务是"为第 u 个元素选一个坑位放进去"。每个元素从所有还空着的坑中挑一个。

这两种视角遍历的是同一棵排列树的"镜像"------一个按行展开,一个按列展开。最终产生的排列集合完全相同,都是 n! 个。
#mermaid-svg-qEi55J7k1ouyX1TT{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-qEi55J7k1ouyX1TT .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-qEi55J7k1ouyX1TT .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-qEi55J7k1ouyX1TT .error-icon{fill:#552222;}#mermaid-svg-qEi55J7k1ouyX1TT .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-qEi55J7k1ouyX1TT .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-qEi55J7k1ouyX1TT .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-qEi55J7k1ouyX1TT .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-qEi55J7k1ouyX1TT .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-qEi55J7k1ouyX1TT .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-qEi55J7k1ouyX1TT .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-qEi55J7k1ouyX1TT .marker{fill:#333333;stroke:#333333;}#mermaid-svg-qEi55J7k1ouyX1TT .marker.cross{stroke:#333333;}#mermaid-svg-qEi55J7k1ouyX1TT svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-qEi55J7k1ouyX1TT p{margin:0;}#mermaid-svg-qEi55J7k1ouyX1TT .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-qEi55J7k1ouyX1TT .cluster-label text{fill:#333;}#mermaid-svg-qEi55J7k1ouyX1TT .cluster-label span{color:#333;}#mermaid-svg-qEi55J7k1ouyX1TT .cluster-label span p{background-color:transparent;}#mermaid-svg-qEi55J7k1ouyX1TT .label text,#mermaid-svg-qEi55J7k1ouyX1TT span{fill:#333;color:#333;}#mermaid-svg-qEi55J7k1ouyX1TT .node rect,#mermaid-svg-qEi55J7k1ouyX1TT .node circle,#mermaid-svg-qEi55J7k1ouyX1TT .node ellipse,#mermaid-svg-qEi55J7k1ouyX1TT .node polygon,#mermaid-svg-qEi55J7k1ouyX1TT .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-qEi55J7k1ouyX1TT .rough-node .label text,#mermaid-svg-qEi55J7k1ouyX1TT .node .label text,#mermaid-svg-qEi55J7k1ouyX1TT .image-shape .label,#mermaid-svg-qEi55J7k1ouyX1TT .icon-shape .label{text-anchor:middle;}#mermaid-svg-qEi55J7k1ouyX1TT .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-qEi55J7k1ouyX1TT .rough-node .label,#mermaid-svg-qEi55J7k1ouyX1TT .node .label,#mermaid-svg-qEi55J7k1ouyX1TT .image-shape .label,#mermaid-svg-qEi55J7k1ouyX1TT .icon-shape .label{text-align:center;}#mermaid-svg-qEi55J7k1ouyX1TT .node.clickable{cursor:pointer;}#mermaid-svg-qEi55J7k1ouyX1TT .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-qEi55J7k1ouyX1TT .arrowheadPath{fill:#333333;}#mermaid-svg-qEi55J7k1ouyX1TT .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-qEi55J7k1ouyX1TT .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-qEi55J7k1ouyX1TT .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-qEi55J7k1ouyX1TT .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-qEi55J7k1ouyX1TT .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-qEi55J7k1ouyX1TT .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-qEi55J7k1ouyX1TT .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-qEi55J7k1ouyX1TT .cluster text{fill:#333;}#mermaid-svg-qEi55J7k1ouyX1TT .cluster span{color:#333;}#mermaid-svg-qEi55J7k1ouyX1TT div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-qEi55J7k1ouyX1TT .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-qEi55J7k1ouyX1TT rect.text{fill:none;stroke-width:0;}#mermaid-svg-qEi55J7k1ouyX1TT .icon-shape,#mermaid-svg-qEi55J7k1ouyX1TT .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-qEi55J7k1ouyX1TT .icon-shape p,#mermaid-svg-qEi55J7k1ouyX1TT .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-qEi55J7k1ouyX1TT .icon-shape .label rect,#mermaid-svg-qEi55J7k1ouyX1TT .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-qEi55J7k1ouyX1TT .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-qEi55J7k1ouyX1TT .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-qEi55J7k1ouyX1TT :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 为元素找坑
元素 0

从所有空坑中挑一个
元素 1

从剩余空坑中挑一个
元素 2

从剩余空坑中挑一个
元素 n-1

只剩最后一个空坑
为坑找元素
坑 0

从所有未选元素中挑
坑 1

从剩余未选元素中挑
坑 2

从剩余未选元素中挑
坑 n-1

只剩最后一个

3.2 写法一:为坑找元素 + used 数组

这是最直观的写法。递归函数 dfs(u) 的含义是"为第 u 个坑做选择"。它需要三个辅助结构:

  • path 数组:记录已经做出的选择序列(当前路径)。
  • used 数组:标记哪些元素已经被选走了。遍历所有元素时跳过 used 为 true 的那些。
  • 循环遍历全部元素:在循环体中做"选择 → 递归 → 撤销"。

这三个结构各司其职:path 负责"记住路径",used 负责"排除已选",循环负责"穷举剩余"。逻辑清晰、容易理解、去重也最方便。面试首选这种写法。

cpp 复制代码
function dfs(u):          // 为第 u 个坑做选择
    if u == n:
        收集 path
        return
    for i in 0..n-1:      // 遍历所有元素
        if used[i]: continue
        path[u] = nums[i]
        used[i] = true
        dfs(u + 1)
        used[i] = false   // 撤销

3.3 写法二:为元素找坑 + 状态压缩

视角反转。递归函数 dfs(u) 的含义是"为第 u 个元素选一个坑"。循环遍历所有坑位,用一个 bitmask(位运算整数)记录哪些坑已经被占了------第 i 位为 1 表示坑 i 已被占。

和写法一的区别仅仅是"谁选谁"的视角不同,以及用位运算代替了 bool 数组。时间复杂度完全一样,但在某些需要极致常数优化的场景(比如 N 皇后用位运算记录列和对角线)中比较常见。

cpp 复制代码
function dfs(u, state):   // 为第 u 个元素选坑, state 记录坑位占用
    if u == n:
        收集 path
        return
    for i in 0..n-1:      // 遍历所有坑位
        if state 第i位为1: continue
        path[i] = nums[u]
        dfs(u + 1, state | (1 << i))

3.4 写法三:交换法------"为坑找元素"的极致优化

交换法的递归函数同样是 dfs(u)------为第 u 个坑做选择。循环 i 从 u 到 n-1,每次把第 i 个元素 swap 到位置 u 上来,然后递归 dfs(u+1),递归返回后再 swap 回去。

cpp 复制代码
function dfs(u):          // 为第 u 个坑做选择
    if u == n:
        收集 nums[0..n-1]  // 数组本身就是结果
        return
    for i in u..n-1:      // 只遍历待选区
        swap(nums[u], nums[i])
        dfs(u + 1)
        swap(nums[u], nums[i])  // 撤销

它看起来和写法一完全不同,但本质仍然是"为坑找元素"。精妙之处在于:一个 swap 操作同时完成了写法一中三个独立数据结构的工作。

怎么理解?关键在于 swap 把数组切成了两段:

  • [0, u-1] 就是 path。这些位置上的元素已经确定了,就是当前路径。不需要额外的 path 数组来存储。
  • [u, n-1] 就是选择列表。还没被选过的元素全都在这段里。循环只遍历这段,天然只看到"还没选过的元素"------不需要 used 数组来标记谁选过了。
  • swap(u, i) 就是"把元素 i 填入坑 u"。它同时完成了"记录选择"和"把这个元素从待选区移走"两件事。递归返回后 swap 回来,就是"撤销选择"并"把这个元素放回待选区"。

#mermaid-svg-w5LKvrVhFvYckj6d{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-w5LKvrVhFvYckj6d .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-w5LKvrVhFvYckj6d .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-w5LKvrVhFvYckj6d .error-icon{fill:#552222;}#mermaid-svg-w5LKvrVhFvYckj6d .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-w5LKvrVhFvYckj6d .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-w5LKvrVhFvYckj6d .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-w5LKvrVhFvYckj6d .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-w5LKvrVhFvYckj6d .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-w5LKvrVhFvYckj6d .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-w5LKvrVhFvYckj6d .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-w5LKvrVhFvYckj6d .marker{fill:#333333;stroke:#333333;}#mermaid-svg-w5LKvrVhFvYckj6d .marker.cross{stroke:#333333;}#mermaid-svg-w5LKvrVhFvYckj6d svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-w5LKvrVhFvYckj6d p{margin:0;}#mermaid-svg-w5LKvrVhFvYckj6d .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-w5LKvrVhFvYckj6d .cluster-label text{fill:#333;}#mermaid-svg-w5LKvrVhFvYckj6d .cluster-label span{color:#333;}#mermaid-svg-w5LKvrVhFvYckj6d .cluster-label span p{background-color:transparent;}#mermaid-svg-w5LKvrVhFvYckj6d .label text,#mermaid-svg-w5LKvrVhFvYckj6d span{fill:#333;color:#333;}#mermaid-svg-w5LKvrVhFvYckj6d .node rect,#mermaid-svg-w5LKvrVhFvYckj6d .node circle,#mermaid-svg-w5LKvrVhFvYckj6d .node ellipse,#mermaid-svg-w5LKvrVhFvYckj6d .node polygon,#mermaid-svg-w5LKvrVhFvYckj6d .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-w5LKvrVhFvYckj6d .rough-node .label text,#mermaid-svg-w5LKvrVhFvYckj6d .node .label text,#mermaid-svg-w5LKvrVhFvYckj6d .image-shape .label,#mermaid-svg-w5LKvrVhFvYckj6d .icon-shape .label{text-anchor:middle;}#mermaid-svg-w5LKvrVhFvYckj6d .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-w5LKvrVhFvYckj6d .rough-node .label,#mermaid-svg-w5LKvrVhFvYckj6d .node .label,#mermaid-svg-w5LKvrVhFvYckj6d .image-shape .label,#mermaid-svg-w5LKvrVhFvYckj6d .icon-shape .label{text-align:center;}#mermaid-svg-w5LKvrVhFvYckj6d .node.clickable{cursor:pointer;}#mermaid-svg-w5LKvrVhFvYckj6d .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-w5LKvrVhFvYckj6d .arrowheadPath{fill:#333333;}#mermaid-svg-w5LKvrVhFvYckj6d .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-w5LKvrVhFvYckj6d .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-w5LKvrVhFvYckj6d .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-w5LKvrVhFvYckj6d .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-w5LKvrVhFvYckj6d .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-w5LKvrVhFvYckj6d .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-w5LKvrVhFvYckj6d .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-w5LKvrVhFvYckj6d .cluster text{fill:#333;}#mermaid-svg-w5LKvrVhFvYckj6d .cluster span{color:#333;}#mermaid-svg-w5LKvrVhFvYckj6d div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-w5LKvrVhFvYckj6d .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-w5LKvrVhFvYckj6d rect.text{fill:none;stroke-width:0;}#mermaid-svg-w5LKvrVhFvYckj6d .icon-shape,#mermaid-svg-w5LKvrVhFvYckj6d .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-w5LKvrVhFvYckj6d .icon-shape p,#mermaid-svg-w5LKvrVhFvYckj6d .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-w5LKvrVhFvYckj6d .icon-shape .label rect,#mermaid-svg-w5LKvrVhFvYckj6d .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-w5LKvrVhFvYckj6d .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-w5LKvrVhFvYckj6d .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-w5LKvrVhFvYckj6d :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} swap 后
swap(u, i)
swap 前

已选 \| 待选

0..u-1\] \[u..n-1

path 区域 -- 选择列表区域
把 numsi 交换到位置 u

= 将元素 i 填入坑 u
已选区扩大

待选区缩小

pathu = numsi

所以交换法的本质是:用数组的物理分区替代了 path 和 used,把三个数据结构压缩进了原数组本身。 这就是为什么它不需要任何额外空间,也不需要任何标记数组------所有信息都编码在数组的当前排列状态里。

swap 让数组自动切成"已选"和"待选"两段,一次操作 = 选择 + 标记 + 移出待选区。

正确性为什么成立?

  • 不漏 :第 u 层循环让 [u, n-1] 中的每个元素都轮流占据位置 u。对于每种选择,递归保证位置 u+1 到 n-1 也穷举了剩余元素的所有排列。用归纳法即可证明 n! 种排列全部被生成。
  • 不重:同一层循环中,不同的 i 值把不同的元素 swap 到位置 u → 这些分支的第 u 个位置元素不同 → 产生的排列必然不同。递归返回后 swap 回来恢复原状,保证各分支之间互不干扰。

为什么面试不推荐用交换法?

两个原因。第一,加上重复元素后去重极其困难------swap 会破坏数组的排序前提,导致常规的 nums[i] == nums[i-1] 去重条件失效。第二,面试时不好口头解释------面试官问"为什么不漏不重",你需要讲清楚数组分区的不变量,这比写法一的 used 数组复杂得多。

了解交换法的精妙,但实战优先用写法一。

三种写法本质都在遍历同一棵排列树,区别是记录状态的方式不同。

3.5 三种写法总览

写法 视角 辅助结构 去重难度 推荐场景
used 数组 为坑找元素 path + used\[\] 面试首选
状态压缩 为元素找坑 path + bitmask 追求常数优化
交换法 为坑找元素(压缩版) 无额外结构 理解原理即可

四、去重:回溯最容易踩的坑

无重复元素的回溯人人都会写。加上重复元素,翻车率直线上升。去重是回溯中最值得花时间搞懂的部分。

4.1 为什么会产生重复

假设输入是 [1₁, 1₂, 2](下标区分两个 1)。如果不做任何处理,全排列会产生:

text 复制代码
[1₁, 1₂, 2]  和  [1₂, 1₁, 2]

这两个排列从数值上看完全一样,都是 [1, 1, 2],但它们对应解空间树上不同的路径------因为选的是不同索引的元素。这就是重复的来源:相同数值、不同索引的元素被当成了不同选择。

4.2 树层去重 vs 树枝去重

要理解去重,先要搞清楚两个概念。还是以 [1₁, 1₂, 2] 的全排列为例,画出解空间树:
#mermaid-svg-iCQwEVh0LHu2GprN{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-iCQwEVh0LHu2GprN .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-iCQwEVh0LHu2GprN .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-iCQwEVh0LHu2GprN .error-icon{fill:#552222;}#mermaid-svg-iCQwEVh0LHu2GprN .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-iCQwEVh0LHu2GprN .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-iCQwEVh0LHu2GprN .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-iCQwEVh0LHu2GprN .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-iCQwEVh0LHu2GprN .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-iCQwEVh0LHu2GprN .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-iCQwEVh0LHu2GprN .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-iCQwEVh0LHu2GprN .marker{fill:#333333;stroke:#333333;}#mermaid-svg-iCQwEVh0LHu2GprN .marker.cross{stroke:#333333;}#mermaid-svg-iCQwEVh0LHu2GprN svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-iCQwEVh0LHu2GprN p{margin:0;}#mermaid-svg-iCQwEVh0LHu2GprN .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-iCQwEVh0LHu2GprN .cluster-label text{fill:#333;}#mermaid-svg-iCQwEVh0LHu2GprN .cluster-label span{color:#333;}#mermaid-svg-iCQwEVh0LHu2GprN .cluster-label span p{background-color:transparent;}#mermaid-svg-iCQwEVh0LHu2GprN .label text,#mermaid-svg-iCQwEVh0LHu2GprN span{fill:#333;color:#333;}#mermaid-svg-iCQwEVh0LHu2GprN .node rect,#mermaid-svg-iCQwEVh0LHu2GprN .node circle,#mermaid-svg-iCQwEVh0LHu2GprN .node ellipse,#mermaid-svg-iCQwEVh0LHu2GprN .node polygon,#mermaid-svg-iCQwEVh0LHu2GprN .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-iCQwEVh0LHu2GprN .rough-node .label text,#mermaid-svg-iCQwEVh0LHu2GprN .node .label text,#mermaid-svg-iCQwEVh0LHu2GprN .image-shape .label,#mermaid-svg-iCQwEVh0LHu2GprN .icon-shape .label{text-anchor:middle;}#mermaid-svg-iCQwEVh0LHu2GprN .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-iCQwEVh0LHu2GprN .rough-node .label,#mermaid-svg-iCQwEVh0LHu2GprN .node .label,#mermaid-svg-iCQwEVh0LHu2GprN .image-shape .label,#mermaid-svg-iCQwEVh0LHu2GprN .icon-shape .label{text-align:center;}#mermaid-svg-iCQwEVh0LHu2GprN .node.clickable{cursor:pointer;}#mermaid-svg-iCQwEVh0LHu2GprN .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-iCQwEVh0LHu2GprN .arrowheadPath{fill:#333333;}#mermaid-svg-iCQwEVh0LHu2GprN .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-iCQwEVh0LHu2GprN .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-iCQwEVh0LHu2GprN .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-iCQwEVh0LHu2GprN .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-iCQwEVh0LHu2GprN .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-iCQwEVh0LHu2GprN .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-iCQwEVh0LHu2GprN .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-iCQwEVh0LHu2GprN .cluster text{fill:#333;}#mermaid-svg-iCQwEVh0LHu2GprN .cluster span{color:#333;}#mermaid-svg-iCQwEVh0LHu2GprN div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-iCQwEVh0LHu2GprN .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-iCQwEVh0LHu2GprN rect.text{fill:none;stroke-width:0;}#mermaid-svg-iCQwEVh0LHu2GprN .icon-shape,#mermaid-svg-iCQwEVh0LHu2GprN .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-iCQwEVh0LHu2GprN .icon-shape p,#mermaid-svg-iCQwEVh0LHu2GprN .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-iCQwEVh0LHu2GprN .icon-shape .label rect,#mermaid-svg-iCQwEVh0LHu2GprN .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-iCQwEVh0LHu2GprN .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-iCQwEVh0LHu2GprN .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-iCQwEVh0LHu2GprN :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 1₁
1₂
2
2
1₂
1₂
1₁
2
2
1₁
2
1₁
1₂
1₂
1₁

  • 树枝去重:在同一条从根到叶的路径上,不能重复选同一个索引位置的元素。比如第一层选了 1₁(索引 0),后面就不能再选索引 0。这件事 used 数组天然保证了,不需要额外逻辑。
  • 树层去重:在同一个父节点的多个子分支中,相同数值的元素只走第一个。比如根节点的三个子分支分别选了 1₁、1₂、2。1₁ 和 1₂ 的数值相同,这两个分支最终会产生完全相同的排列集合。所以 1₂ 这个分支可以整个跳过。

红色 X 标记的分支是树层去重剪掉的;绿色分支是 used 数组天然保证不重复选同一索引。

树层去重才是我们需要手动处理的。 它的核心逻辑是:如果当前元素和前一个元素值相同,并且前一个元素在本层还没有被使用,那就跳过当前元素。

4.3 经典去重条件:排序 + usedi-1 判定

具体做法分两步:

第一步:先排序。排序后相同的元素相邻,这是后续判定的前提。

第二步:在循环中加跳过条件。当同时满足以下两个条件时,跳过当前元素 i:

  • nums[i] == nums[i-1](当前元素和前一个元素值相同)
  • used[i-1] == false(前一个元素当前没有被使用)

要真正理解这个条件,我们用 [1₁, 1₂, 2] 的全排列来逐步推演。

场景 A:第一层选 1₁ 后,进入第二层

此时 used = [true, false, false]。第二层循环到 i=1(即 1₂)时检查条件:

  • nums[1] == nums[0]? → 是(都是 1)
  • used[0] == false? → 否(used0 = true,说明 1₁ 已经在当前路径中)

条件不满足,不跳过。1₂ 被允许选入第二层。这是合理的------路径 [1₁, 1₂, ...] 是一个合法排列。

场景 B:第一层跳过 1₁,尝试选 1₂

此时 used = [false, false, false](1₁ 还没被选)。第一层循环到 i=1(即 1₂)时检查条件:

  • nums[1] == nums[0]? → 是
  • used[0] == false? → 是(1₁ 没有在当前路径中)

条件满足,跳过 1₂。

为什么跳过是对的?因为 1₁ 还没被使用,却在同一层被跳过了(或者说还没轮到它进入当前路径)------这说明我们是在尝试用 1₂ "替代" 1₁ 的位置。而 1₁ 和 1₂ 数值相同,替代产生的结果完全一样,所以是多余分支。

总结这条规则的直觉:

对于数值相同的一组元素,我们强制规定一个使用顺序------必须按索引从小到大使用。 也就是说 1₂ 只有在 1₁ 已经被选进当前路径(used[i-1] == true)的前提下,才被允许使用。如果 1₁ 还空着你就想用 1₂,说明你在"插队",会产生重复,跳过。

这条规则保证了:相同数值的元素在最终排列中只会以一种相对顺序出现(1₁ 在 1₂ 前面),从而消除所有因"交换相同元素顺序"而产生的重复排列。

4.4 usedi-1 == false 还是 true?两种写法的本质区别

你可能在不同教程里见过两种跳过条件:

  • 写法 Anums[i] == nums[i-1] && !used[i-1] → 跳过
  • 写法 Bnums[i] == nums[i-1] && used[i-1] → 跳过

两种都能得到正确的去重结果(不会有重复排列),但它们施加的约束方向相反,剪掉的分支位置不同,效率也不同。

  • 写法 A 的含义(推荐):"如果前一个相同元素没在当前路径中,就不允许选当前元素。"效果:强制相同元素按原始索引从小到大使用。1₁ 必须先于 1₂ 被选入路径。

  • 写法 B 的含义:"如果前一个相同元素已经在当前路径中,就不允许选当前元素。"效果:强制相同元素按原始索引从大到小使用。1₂ 必须先于 1₁ 被选入路径。

两种约束都能保证"相同值的元素只以一种固定相对顺序出现",因此都能消除重复。但它们的剪枝位置有本质差异。
#mermaid-svg-giKkGIur0leZolN8{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-giKkGIur0leZolN8 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-giKkGIur0leZolN8 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-giKkGIur0leZolN8 .error-icon{fill:#552222;}#mermaid-svg-giKkGIur0leZolN8 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-giKkGIur0leZolN8 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-giKkGIur0leZolN8 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-giKkGIur0leZolN8 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-giKkGIur0leZolN8 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-giKkGIur0leZolN8 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-giKkGIur0leZolN8 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-giKkGIur0leZolN8 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-giKkGIur0leZolN8 .marker.cross{stroke:#333333;}#mermaid-svg-giKkGIur0leZolN8 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-giKkGIur0leZolN8 p{margin:0;}#mermaid-svg-giKkGIur0leZolN8 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-giKkGIur0leZolN8 .cluster-label text{fill:#333;}#mermaid-svg-giKkGIur0leZolN8 .cluster-label span{color:#333;}#mermaid-svg-giKkGIur0leZolN8 .cluster-label span p{background-color:transparent;}#mermaid-svg-giKkGIur0leZolN8 .label text,#mermaid-svg-giKkGIur0leZolN8 span{fill:#333;color:#333;}#mermaid-svg-giKkGIur0leZolN8 .node rect,#mermaid-svg-giKkGIur0leZolN8 .node circle,#mermaid-svg-giKkGIur0leZolN8 .node ellipse,#mermaid-svg-giKkGIur0leZolN8 .node polygon,#mermaid-svg-giKkGIur0leZolN8 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-giKkGIur0leZolN8 .rough-node .label text,#mermaid-svg-giKkGIur0leZolN8 .node .label text,#mermaid-svg-giKkGIur0leZolN8 .image-shape .label,#mermaid-svg-giKkGIur0leZolN8 .icon-shape .label{text-anchor:middle;}#mermaid-svg-giKkGIur0leZolN8 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-giKkGIur0leZolN8 .rough-node .label,#mermaid-svg-giKkGIur0leZolN8 .node .label,#mermaid-svg-giKkGIur0leZolN8 .image-shape .label,#mermaid-svg-giKkGIur0leZolN8 .icon-shape .label{text-align:center;}#mermaid-svg-giKkGIur0leZolN8 .node.clickable{cursor:pointer;}#mermaid-svg-giKkGIur0leZolN8 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-giKkGIur0leZolN8 .arrowheadPath{fill:#333333;}#mermaid-svg-giKkGIur0leZolN8 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-giKkGIur0leZolN8 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-giKkGIur0leZolN8 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-giKkGIur0leZolN8 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-giKkGIur0leZolN8 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-giKkGIur0leZolN8 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-giKkGIur0leZolN8 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-giKkGIur0leZolN8 .cluster text{fill:#333;}#mermaid-svg-giKkGIur0leZolN8 .cluster span{color:#333;}#mermaid-svg-giKkGIur0leZolN8 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-giKkGIur0leZolN8 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-giKkGIur0leZolN8 rect.text{fill:none;stroke-width:0;}#mermaid-svg-giKkGIur0leZolN8 .icon-shape,#mermaid-svg-giKkGIur0leZolN8 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-giKkGIur0leZolN8 .icon-shape p,#mermaid-svg-giKkGIur0leZolN8 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-giKkGIur0leZolN8 .icon-shape .label rect,#mermaid-svg-giKkGIur0leZolN8 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-giKkGIur0leZolN8 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-giKkGIur0leZolN8 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-giKkGIur0leZolN8 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 写法 B: usedi-1
第一层存活分支

1₁, 1₂, 1₃, 2 (4个)
去重位置: 树枝

进入子树后才发现冲突
搜索量: 更多
写法 A: !usedi-1 ✅ 推荐
第一层存活分支

1₁, 2 (2个)
去重位置: 树层

同层直接跳过多余分支
搜索量: 更少

对比维度 写法 A(!usedi-1 写法 B(usedi-1
第一层存活分支 1₁、2(2个) 1₁、1₂、1₃、2(4个)
去重发生位置 树的浅层(同层直接跳过多余分支) 树的深层(进入子树后才发现冲突)
搜索节点总数 更少 更多

写法 A 是树层去重 :在同一父节点的多个分支中,相同值只保留一个,多余分支根本不展开。写法 B 是树枝去重:允许同值分支全部展开,但在树枝深处通过约束让它们无法产生完整排列,最终也能得到正确结果------但走了更多弯路。

用一个直觉类比:写法 A 好比在路口就把重复的岔路封死,写法 B 好比让你走进去之后发现此路不通再回头。两种方式都不会让你走错路,但前者明显少走很多冤枉路。

面试建议:用写法 A(!used[i-1],理由一句话讲清------"相同值的元素必须按索引升序使用,不按顺序就跳过。这样在每一层都只保留一个代表,树层去重,搜索量最小。"

4.5 不能排序时怎么办:HashSet 去重

有些题目不能排序。最典型的例子是"递增子序列"------你需要找出数组中所有递增的子序列,如果先排序,原始顺序就被破坏了。

这种情况下,在每一层维护一个 HashSet,记录"本层已经选过哪些值"。如果当前元素的值已经在 set 里,说明本层已经有一个相同值的元素走过这条分支了,跳过。

代价是每层额外创建一个 set,空间开销比排序方案大。但思路最通用,不依赖排序。
#mermaid-svg-HmKExSnZu83YlpXS{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-HmKExSnZu83YlpXS .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-HmKExSnZu83YlpXS .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-HmKExSnZu83YlpXS .error-icon{fill:#552222;}#mermaid-svg-HmKExSnZu83YlpXS .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-HmKExSnZu83YlpXS .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-HmKExSnZu83YlpXS .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-HmKExSnZu83YlpXS .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-HmKExSnZu83YlpXS .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-HmKExSnZu83YlpXS .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-HmKExSnZu83YlpXS .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-HmKExSnZu83YlpXS .marker{fill:#333333;stroke:#333333;}#mermaid-svg-HmKExSnZu83YlpXS .marker.cross{stroke:#333333;}#mermaid-svg-HmKExSnZu83YlpXS svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-HmKExSnZu83YlpXS p{margin:0;}#mermaid-svg-HmKExSnZu83YlpXS .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-HmKExSnZu83YlpXS .cluster-label text{fill:#333;}#mermaid-svg-HmKExSnZu83YlpXS .cluster-label span{color:#333;}#mermaid-svg-HmKExSnZu83YlpXS .cluster-label span p{background-color:transparent;}#mermaid-svg-HmKExSnZu83YlpXS .label text,#mermaid-svg-HmKExSnZu83YlpXS span{fill:#333;color:#333;}#mermaid-svg-HmKExSnZu83YlpXS .node rect,#mermaid-svg-HmKExSnZu83YlpXS .node circle,#mermaid-svg-HmKExSnZu83YlpXS .node ellipse,#mermaid-svg-HmKExSnZu83YlpXS .node polygon,#mermaid-svg-HmKExSnZu83YlpXS .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-HmKExSnZu83YlpXS .rough-node .label text,#mermaid-svg-HmKExSnZu83YlpXS .node .label text,#mermaid-svg-HmKExSnZu83YlpXS .image-shape .label,#mermaid-svg-HmKExSnZu83YlpXS .icon-shape .label{text-anchor:middle;}#mermaid-svg-HmKExSnZu83YlpXS .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-HmKExSnZu83YlpXS .rough-node .label,#mermaid-svg-HmKExSnZu83YlpXS .node .label,#mermaid-svg-HmKExSnZu83YlpXS .image-shape .label,#mermaid-svg-HmKExSnZu83YlpXS .icon-shape .label{text-align:center;}#mermaid-svg-HmKExSnZu83YlpXS .node.clickable{cursor:pointer;}#mermaid-svg-HmKExSnZu83YlpXS .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-HmKExSnZu83YlpXS .arrowheadPath{fill:#333333;}#mermaid-svg-HmKExSnZu83YlpXS .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-HmKExSnZu83YlpXS .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-HmKExSnZu83YlpXS .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-HmKExSnZu83YlpXS .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-HmKExSnZu83YlpXS .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-HmKExSnZu83YlpXS .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-HmKExSnZu83YlpXS .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-HmKExSnZu83YlpXS .cluster text{fill:#333;}#mermaid-svg-HmKExSnZu83YlpXS .cluster span{color:#333;}#mermaid-svg-HmKExSnZu83YlpXS div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-HmKExSnZu83YlpXS .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-HmKExSnZu83YlpXS rect.text{fill:none;stroke-width:0;}#mermaid-svg-HmKExSnZu83YlpXS .icon-shape,#mermaid-svg-HmKExSnZu83YlpXS .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-HmKExSnZu83YlpXS .icon-shape p,#mermaid-svg-HmKExSnZu83YlpXS .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-HmKExSnZu83YlpXS .icon-shape .label rect,#mermaid-svg-HmKExSnZu83YlpXS .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-HmKExSnZu83YlpXS .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-HmKExSnZu83YlpXS .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-HmKExSnZu83YlpXS :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是

进入递归第 u 层
创建 HashSet

记录本层已选值
遍历候选元素
元素值已在

HashSet 中?
跳过该元素

树层去重
加入 HashSet
选择该元素
递归下一层

4.6 组合去重 vs 排列去重

最后理清一个容易混淆的点:组合和排列的去重逻辑是不同的。

  • 组合去重的核心是 startIndex 。组合不关心顺序,[1, 2][2, 1] 是同一个组合。所以递归时传一个 startIndex,每次只往后选、不回头------这就天然避免了顺序重复。如果元素有重复值,再叠加"排序 + nums[i] == nums[i-1] 跳过"即可。

  • 排列去重的核心是 used 数组 + 树层去重 。排列关心顺序,不能用 startIndex(因为 [1, 2][2, 1] 是两个不同的排列)。去重只能靠 used 数组配合 !used[i-1] 条件来做树层去重。

组合靠 startIndex 天然避免顺序重复;排列靠 used + 树层去重避免重复值产生相同排列。

一句话总结:组合用 startIndex 管顺序,排列用 used 管选择;有重复值时,两者都需要额外的树层去重。

五、五类高频题型速览

搞清楚解空间树和去重之后,我们快速过一遍五类高频回溯题。每类给出问题特征、建模方式、关键技巧和面试一句话表达。
#mermaid-svg-PbDCEaNssx148ypd{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-PbDCEaNssx148ypd .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-PbDCEaNssx148ypd .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-PbDCEaNssx148ypd .error-icon{fill:#552222;}#mermaid-svg-PbDCEaNssx148ypd .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-PbDCEaNssx148ypd .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-PbDCEaNssx148ypd .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-PbDCEaNssx148ypd .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-PbDCEaNssx148ypd .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-PbDCEaNssx148ypd .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-PbDCEaNssx148ypd .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-PbDCEaNssx148ypd .marker{fill:#333333;stroke:#333333;}#mermaid-svg-PbDCEaNssx148ypd .marker.cross{stroke:#333333;}#mermaid-svg-PbDCEaNssx148ypd svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-PbDCEaNssx148ypd p{margin:0;}#mermaid-svg-PbDCEaNssx148ypd .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-PbDCEaNssx148ypd .cluster-label text{fill:#333;}#mermaid-svg-PbDCEaNssx148ypd .cluster-label span{color:#333;}#mermaid-svg-PbDCEaNssx148ypd .cluster-label span p{background-color:transparent;}#mermaid-svg-PbDCEaNssx148ypd .label text,#mermaid-svg-PbDCEaNssx148ypd span{fill:#333;color:#333;}#mermaid-svg-PbDCEaNssx148ypd .node rect,#mermaid-svg-PbDCEaNssx148ypd .node circle,#mermaid-svg-PbDCEaNssx148ypd .node ellipse,#mermaid-svg-PbDCEaNssx148ypd .node polygon,#mermaid-svg-PbDCEaNssx148ypd .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-PbDCEaNssx148ypd .rough-node .label text,#mermaid-svg-PbDCEaNssx148ypd .node .label text,#mermaid-svg-PbDCEaNssx148ypd .image-shape .label,#mermaid-svg-PbDCEaNssx148ypd .icon-shape .label{text-anchor:middle;}#mermaid-svg-PbDCEaNssx148ypd .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-PbDCEaNssx148ypd .rough-node .label,#mermaid-svg-PbDCEaNssx148ypd .node .label,#mermaid-svg-PbDCEaNssx148ypd .image-shape .label,#mermaid-svg-PbDCEaNssx148ypd .icon-shape .label{text-align:center;}#mermaid-svg-PbDCEaNssx148ypd .node.clickable{cursor:pointer;}#mermaid-svg-PbDCEaNssx148ypd .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-PbDCEaNssx148ypd .arrowheadPath{fill:#333333;}#mermaid-svg-PbDCEaNssx148ypd .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-PbDCEaNssx148ypd .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-PbDCEaNssx148ypd .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-PbDCEaNssx148ypd .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-PbDCEaNssx148ypd .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-PbDCEaNssx148ypd .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-PbDCEaNssx148ypd .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-PbDCEaNssx148ypd .cluster text{fill:#333;}#mermaid-svg-PbDCEaNssx148ypd .cluster span{color:#333;}#mermaid-svg-PbDCEaNssx148ypd div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-PbDCEaNssx148ypd .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-PbDCEaNssx148ypd rect.text{fill:none;stroke-width:0;}#mermaid-svg-PbDCEaNssx148ypd .icon-shape,#mermaid-svg-PbDCEaNssx148ypd .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-PbDCEaNssx148ypd .icon-shape p,#mermaid-svg-PbDCEaNssx148ypd .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-PbDCEaNssx148ypd .icon-shape .label rect,#mermaid-svg-PbDCEaNssx148ypd .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-PbDCEaNssx148ypd .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-PbDCEaNssx148ypd .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-PbDCEaNssx148ypd :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 五类回溯题
组合类

子集树 + startIndex
排列类

排列树 + used 数组
子集类

子集树, 每节点收集
棋盘搜索类

排列树 / 网格 DFS
切割类

子集树, 枚举切点
N选K / 组合总和
全排列 / 含重复
所有子集 / 递增子序列
N 皇后 / 单词搜索
分割回文串 / 复原 IP

5.1 组合类

问题特征:从 n 个元素中选 k 个(不关心顺序),或者选若干个使得和为 target。

建模:子集树。每个元素"选或不选"。用 startIndex 保证只往后选。

关键技巧:

  • 剩余元素不够 k 个 → 直接返回
  • 排序后 sum 已超 target → 提前终止本层循环
  • 有重复元素时 → 排序 + 跳过相邻相同值(树层去重)

面试一句话:"这是个组合问题,我用子集树建模,startIndex 避免顺序重复,排序后提前终止来加速。"

对应题目:N选K组合、组合总和(无重复版/有重复版/可重选版)、和为 n 的 K 个数。

5.2 排列类

问题特征:给定 n 个元素,输出所有不同的排列(关心顺序)。

建模:排列树。用"为坑找元素 + used 数组"的写法。

关键技巧:

  • 无重复元素:used 数组即可
  • 有重复元素:排序 + !used[i-1] 树层去重

面试一句话:"排列树,used 数组标记已选元素。有重复时排序后树层去重------相同值在同一层只选第一个。"

对应题目:全排列(无重复/有重复)。

5.3 子集类

问题特征:求集合的所有子集,或者满足某些条件的所有子序列。

建模:子集树。与组合的区别在于------每个节点都收集答案,而不只是叶节点。因为空集、只选一个元素、选两个元素......都是合法的子集。

关键技巧:

  • 所有子集:进入递归就先把当前 path 收集进结果集
  • 递增子序列:不能排序(会破坏原始顺序)→ 用 HashSet 做树层去重

面试一句话:"子集树,每个节点都收集答案。递增子序列不能排序,用 set 去重。"

对应题目:所有子集、递增子序列。

5.4 棋盘与网格搜索类

问题特征:在一个二维网格上搜索满足条件的路径或布局。

建模:

  • N 皇后:排列树。每行选哪一列放皇后。用位运算记录列、主对角线、副对角线的占用情况,一个整数代替三个数组。
  • 单词搜索:从网格中的每个格子出发做 DFS,每步向四个方向扩展。用"原地标记"替代 visited 数组------访问一个格子时把它的字符改成特殊标记,回溯时恢复原值。
  • 机器人路径:类似单词搜索,四方向 DFS + visited。约束条件是坐标的数位之和不超过阈值。

面试一句话:"N 皇后用排列树建模,位运算记录状态。单词搜索用网格 DFS + 原地标记。"

对应题目:N 皇后、单词搜索、机器人可达格子数。

5.5 切割与分割类

问题特征:把一个字符串/序列切成若干段,使得每一段都满足某个条件。

建模:子集树。在字符串的每个位置决定"切不切"。也可以理解为"枚举当前段的结束位置"。

关键技巧:

  • 当前段不合法(不是回文 / 不是合法 IP 段)→ 立即跳过,不进入递归
  • 分割回文串:可以预处理回文判定表,避免每次重新判断

面试一句话:"枚举每一刀的切割位置,当前段不合法就跳过。"

对应题目:分割回文串、复原 IP 地址、序列切分。
#mermaid-svg-6IGR31xMYI4qwckK{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-6IGR31xMYI4qwckK .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-6IGR31xMYI4qwckK .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-6IGR31xMYI4qwckK .error-icon{fill:#552222;}#mermaid-svg-6IGR31xMYI4qwckK .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-6IGR31xMYI4qwckK .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-6IGR31xMYI4qwckK .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-6IGR31xMYI4qwckK .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-6IGR31xMYI4qwckK .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-6IGR31xMYI4qwckK .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-6IGR31xMYI4qwckK .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-6IGR31xMYI4qwckK .marker{fill:#333333;stroke:#333333;}#mermaid-svg-6IGR31xMYI4qwckK .marker.cross{stroke:#333333;}#mermaid-svg-6IGR31xMYI4qwckK svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-6IGR31xMYI4qwckK p{margin:0;}#mermaid-svg-6IGR31xMYI4qwckK .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-6IGR31xMYI4qwckK .cluster-label text{fill:#333;}#mermaid-svg-6IGR31xMYI4qwckK .cluster-label span{color:#333;}#mermaid-svg-6IGR31xMYI4qwckK .cluster-label span p{background-color:transparent;}#mermaid-svg-6IGR31xMYI4qwckK .label text,#mermaid-svg-6IGR31xMYI4qwckK span{fill:#333;color:#333;}#mermaid-svg-6IGR31xMYI4qwckK .node rect,#mermaid-svg-6IGR31xMYI4qwckK .node circle,#mermaid-svg-6IGR31xMYI4qwckK .node ellipse,#mermaid-svg-6IGR31xMYI4qwckK .node polygon,#mermaid-svg-6IGR31xMYI4qwckK .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-6IGR31xMYI4qwckK .rough-node .label text,#mermaid-svg-6IGR31xMYI4qwckK .node .label text,#mermaid-svg-6IGR31xMYI4qwckK .image-shape .label,#mermaid-svg-6IGR31xMYI4qwckK .icon-shape .label{text-anchor:middle;}#mermaid-svg-6IGR31xMYI4qwckK .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-6IGR31xMYI4qwckK .rough-node .label,#mermaid-svg-6IGR31xMYI4qwckK .node .label,#mermaid-svg-6IGR31xMYI4qwckK .image-shape .label,#mermaid-svg-6IGR31xMYI4qwckK .icon-shape .label{text-align:center;}#mermaid-svg-6IGR31xMYI4qwckK .node.clickable{cursor:pointer;}#mermaid-svg-6IGR31xMYI4qwckK .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-6IGR31xMYI4qwckK .arrowheadPath{fill:#333333;}#mermaid-svg-6IGR31xMYI4qwckK .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-6IGR31xMYI4qwckK .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-6IGR31xMYI4qwckK .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-6IGR31xMYI4qwckK .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-6IGR31xMYI4qwckK .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-6IGR31xMYI4qwckK .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-6IGR31xMYI4qwckK .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-6IGR31xMYI4qwckK .cluster text{fill:#333;}#mermaid-svg-6IGR31xMYI4qwckK .cluster span{color:#333;}#mermaid-svg-6IGR31xMYI4qwckK div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-6IGR31xMYI4qwckK .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-6IGR31xMYI4qwckK rect.text{fill:none;stroke-width:0;}#mermaid-svg-6IGR31xMYI4qwckK .icon-shape,#mermaid-svg-6IGR31xMYI4qwckK .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-6IGR31xMYI4qwckK .icon-shape p,#mermaid-svg-6IGR31xMYI4qwckK .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-6IGR31xMYI4qwckK .icon-shape .label rect,#mermaid-svg-6IGR31xMYI4qwckK .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-6IGR31xMYI4qwckK .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-6IGR31xMYI4qwckK .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-6IGR31xMYI4qwckK :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 字符串 'aab'
位置 1 切

'a' 是回文 ✅
位置 2 切

'aa' 是回文 ✅
位置 2 切

'a' 是回文 ✅
位置 3 切

'ab' 不是回文 ❌

跳过
位置 3 切

'b' 是回文 ✅
结果: 'a','a','b'
位置 3 切

'b' 是回文 ✅
结果: 'aa','b'

六、面试实战表达

6.1 面试官考的是什么

面试官出回溯题,考的不是你能不能手写 40 行递归------而是你能不能:

  1. 画出解空间树:告诉他这道题的解空间是什么形状、搜索量的上界是多少
  2. 讲清建模选择:为什么选子集树/排列树、用什么方式记录状态
  3. 讲清去重逻辑:如果有重复元素,你怎么避免重复答案,为什么你的方案是正确的

思路清晰远比代码完整重要。很多候选人急着写代码,写着写着发现去重没处理,然后在代码里打补丁越打越乱。正确的做法是先把树画出来、把去重策略想清楚,再动手写。

6.2 回溯 vs DP 的边界

最后一个常见困惑:什么时候用回溯,什么时候用 DP?
#mermaid-svg-ZrY7wViAMnCyv9j7{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ZrY7wViAMnCyv9j7 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ZrY7wViAMnCyv9j7 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ZrY7wViAMnCyv9j7 .error-icon{fill:#552222;}#mermaid-svg-ZrY7wViAMnCyv9j7 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ZrY7wViAMnCyv9j7 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ZrY7wViAMnCyv9j7 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ZrY7wViAMnCyv9j7 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ZrY7wViAMnCyv9j7 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ZrY7wViAMnCyv9j7 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ZrY7wViAMnCyv9j7 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ZrY7wViAMnCyv9j7 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ZrY7wViAMnCyv9j7 .marker.cross{stroke:#333333;}#mermaid-svg-ZrY7wViAMnCyv9j7 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ZrY7wViAMnCyv9j7 p{margin:0;}#mermaid-svg-ZrY7wViAMnCyv9j7 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ZrY7wViAMnCyv9j7 .cluster-label text{fill:#333;}#mermaid-svg-ZrY7wViAMnCyv9j7 .cluster-label span{color:#333;}#mermaid-svg-ZrY7wViAMnCyv9j7 .cluster-label span p{background-color:transparent;}#mermaid-svg-ZrY7wViAMnCyv9j7 .label text,#mermaid-svg-ZrY7wViAMnCyv9j7 span{fill:#333;color:#333;}#mermaid-svg-ZrY7wViAMnCyv9j7 .node rect,#mermaid-svg-ZrY7wViAMnCyv9j7 .node circle,#mermaid-svg-ZrY7wViAMnCyv9j7 .node ellipse,#mermaid-svg-ZrY7wViAMnCyv9j7 .node polygon,#mermaid-svg-ZrY7wViAMnCyv9j7 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ZrY7wViAMnCyv9j7 .rough-node .label text,#mermaid-svg-ZrY7wViAMnCyv9j7 .node .label text,#mermaid-svg-ZrY7wViAMnCyv9j7 .image-shape .label,#mermaid-svg-ZrY7wViAMnCyv9j7 .icon-shape .label{text-anchor:middle;}#mermaid-svg-ZrY7wViAMnCyv9j7 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ZrY7wViAMnCyv9j7 .rough-node .label,#mermaid-svg-ZrY7wViAMnCyv9j7 .node .label,#mermaid-svg-ZrY7wViAMnCyv9j7 .image-shape .label,#mermaid-svg-ZrY7wViAMnCyv9j7 .icon-shape .label{text-align:center;}#mermaid-svg-ZrY7wViAMnCyv9j7 .node.clickable{cursor:pointer;}#mermaid-svg-ZrY7wViAMnCyv9j7 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ZrY7wViAMnCyv9j7 .arrowheadPath{fill:#333333;}#mermaid-svg-ZrY7wViAMnCyv9j7 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ZrY7wViAMnCyv9j7 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ZrY7wViAMnCyv9j7 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ZrY7wViAMnCyv9j7 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ZrY7wViAMnCyv9j7 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ZrY7wViAMnCyv9j7 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ZrY7wViAMnCyv9j7 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ZrY7wViAMnCyv9j7 .cluster text{fill:#333;}#mermaid-svg-ZrY7wViAMnCyv9j7 .cluster span{color:#333;}#mermaid-svg-ZrY7wViAMnCyv9j7 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-ZrY7wViAMnCyv9j7 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ZrY7wViAMnCyv9j7 rect.text{fill:none;stroke-width:0;}#mermaid-svg-ZrY7wViAMnCyv9j7 .icon-shape,#mermaid-svg-ZrY7wViAMnCyv9j7 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ZrY7wViAMnCyv9j7 .icon-shape p,#mermaid-svg-ZrY7wViAMnCyv9j7 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ZrY7wViAMnCyv9j7 .icon-shape .label rect,#mermaid-svg-ZrY7wViAMnCyv9j7 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ZrY7wViAMnCyv9j7 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ZrY7wViAMnCyv9j7 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ZrY7wViAMnCyv9j7 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是



不能

需要什么结果?
需要列出所有

具体方案?
回溯

DFS 遍历解空间树
只需要方案数

或最优值?
DP 状态能定义?

状态空间不爆炸?
动态规划 ✅

效率最高
其他方法

  • 需要列出所有具体方案 → 回溯。DP 只能告诉你"有多少种方案"或"最优值是多少",但不能把每个方案的细节还原出来。
  • 只需要方案数或最优值 → 优先考虑 DP。DP 通过记表避免重复计算,效率通常远高于回溯。
  • DP 的状态定义不出来,或者状态空间爆炸 → 退回回溯。比如某些约束极复杂的搜索问题,状态空间太大不适合用表存储,回溯加上适当的条件提前终止反而更实用。

一句话判断:要"方案集合"用回溯,要"方案数量或最优值"用 DP。

结尾

回溯 = 在解空间树上做 DFS。子集树和排列树覆盖了 90% 的回溯题。

全排列有三种写法------"为坑找元素"、"为元素找坑"、"交换法"------但交换法本质上是"为坑找元素"的极致压缩版,用数组的物理分区替代了 path 和 used。面试首选 used 数组的写法,交换法了解原理即可。

去重是回溯最容易翻车的地方。核心是分清树层去重和树枝去重:树枝去重靠 used 数组天然保证;树层去重靠"排序 + 相同值同层只选第一个"。不能排序时用 HashSet。组合用 startIndex 管顺序,排列用 used 管选择。

历史文章(算法解析专栏)

算法:双指针:从 O(n²) 到 O(n) 的核心武器

算法:链表题核心技巧与解题框架

算法:栈 - 不是容器,而是历史压缩工具

算法:动态规划 - 从暴力递归到高效填表

相关推荐
气泡音人声分离2 小时前
技术解析|均衡器(EQ)工作原理与实操指南:从频率拆分到听感优化
算法·均衡器·音频剪辑
weixin_413063212 小时前
复现 MatchED 边缘检测模型(单张图片重复8次,训练200 epoch)
python·算法·计算机视觉·边缘检测模型
2601_962440842 小时前
计算机毕业设计之jsp教室管理系统
java·开发语言·笔记·分布式·算法·课程设计·推荐算法
AI视频剪辑官2 小时前
播客切片工具选型核心评价维度
网络·人工智能·算法
复杂网络5 小时前
AI 不睡觉,但它比你更会做实验
算法
贵慜_Derek5 小时前
MAI-04|干净数据在工程上意味着什么:MAI 预训练数据治理
人工智能·算法·llm
想吃火锅10057 小时前
【leetcode】146.LRU缓存js
算法·leetcode·缓存