回溯算法框架

raw/articles/2026/05/回溯算法框架

核心思想

回溯算法本质是遍历一棵决策树的过程,树的每个叶子节点存放着一个合法答案。把整棵树遍历一遍,收集所有叶子节点的答案,就得到所有合法答案。

回溯算法与 DFS 算法基本可认为是同一种算法,细微差异在于实现视角(参见DFS回溯算法的关系)。

回溯算法的三个关键问题

站在回溯树的某个节点上,需要思考 3 个问题:

  1. 路径:已经做出的选择
  2. 选择列表:当前可以做的选择
  3. 结束条件:到达决策树底层,无法再做选择的条件

算法框架

result = []
def backtrack(路径, 选择列表):
    if 满足结束条件:
        result.add(路径)
        return

    for 选择 in 选择列表:
        做选择
        backtrack(路径, 选择列表)
        撤销选择

核心:for 循环里面的递归,在递归调用之前「做选择」,在递归调用之后「撤销选择」。

全排列问题详解

以 LeetCode 46(全排列)为例,输入数组 nums,返回所有数字的全排列。

决策树模型

全排列问题可以抽象为一棵决策树(回溯树):

images/backtracking-1.jpg

站在树的某个节点上(红色节点):

images/backtracking-2.jpg

每个节点的属性(路径、选择列表):

images/backtracking-3.jpg

前序遍历和后序遍历的时间点:

images/backtracking-4.jpg

在递归前后做选择和撤销选择:

images/backtracking-5.jpg

使用 used 数组推导选择列表:

images/backtracking-6.jpg

总结:

  • 路径 [2]:记录已经做过的选择
  • 选择列表 [1, 3]:当前可以做的选择
  • 结束条件:选择列表为空(或路径长度等于数组长度)

代码实现(Java)

class Solution {
    List<List<Integer>> res = new LinkedList<>();

    List<List<Integer>> permute(int[] nums) {
        LinkedList<Integer> track = new LinkedList<>();
        boolean[] used = new boolean[nums.length];
        backtrack(nums, track, used);
        return res;
    }

    void backtrack(int[] nums, LinkedList<Integer> track, boolean[] used) {
        // 触发结束条件
        if (track.size() == nums.length) {
            res.add(new LinkedList(track));
            return;
        }

        for (int i = 0; i < nums.length; i++) {
            if (used[i]) {
                continue; // nums[i] 已经在 track 中,跳过
            }
            // 做选择
            track.add(nums[i]);
            used[i] = true;
            // 进入下一层决策树
            backtrack(nums, track, used);
            // 取消选择
            track.removeLast();
            used[i] = false;
        }
    }
}

关键点

  • 使用 used 数组记录已选择的元素,推导出当前的选择列表
  • 时间复杂度:O(n!),必须穷举整棵决策树
  • 回溯算法是纯暴力穷举,不像动态规划存在重叠子问题可以优化

与动态规划的关系

维度 回溯算法 动态规划
抽象模型 决策树 状态转移图
核心三要素 路径、选择列表、结束条件 状态、选择、base case
优化空间 无(纯暴力穷举) 有(记忆化/重叠子问题)

相关概念


Interactive Graph