回溯算法(Backtracking)常被称为“通用的解题法”。如果把解决一个问题比作走迷宫,回溯算法就是那个不撞南墙不回头的勇者。

什么是回溯算法?

从本质上讲,回溯是一种深度优先搜索(DFS)。它的核心思想是:在搜索过程中,当发现当前的路径已经不能满足求解条件时,就“回退”到上一步,换一条路径继续试探。

D2 Diagram
qtopie.github.io

核心三个步骤(回溯模板):

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

伪代码框架

func permute(nums []int) [][]int {
    var results [][]int
    var path []int
    used := make([]bool, len(nums)) // 状态标记数组

    // 声明闭包函数,以便递归调用
    var backtrack func()
    backtrack = func() {
        // 终止条件:路径长度等于输入数组长度
        if len(path) == len(nums) {
            // 重点:Go 的 slice 是引用传递,必须 copy 一份快照
            results = append(results, append([]int(nil), path...))
            return
        }

        for i := 0; i < len(nums); i++ {
            // --- 剪枝:如果已经使用过该元素,跳过 ---
            if used[i] {
                continue
            }

            // 做选择
            used[i] = true
            path = append(path, nums[i])

            // 递归进入下一层
            backtrack()

            // 撤销选择(回溯)
            path = path[:len(path)-1]
            used[i] = false
        }
    }

    backtrack()
    return results
}

为什么我们要“撤销选择”?

这是回溯算法的灵魂。因为我们是在同一套变量(比如一个数组)上进行操作,如果不撤销刚才的操作,当你退回到上一层节点去走另一条分支时,之前的脏数据会干扰新的搜索。


示例

给你的挑战:全排列问题

既然掌握了理论,咱们直接上实战。这是一道非常经典的入门题,能帮你快速建立“回溯感”。

题目描述: 给定一个不含重复数字的数组 nums(例如 [1, 2, 3]),请你返回其所有可能的全排列

小提示: > 你需要一个 used 数组或集合来标记哪些数字已经被填入当前的路径中了,这样下一层递归就不会重复选它。