面试知识库

回溯模板:排列、组合、子集问题的通用框架#

回溯本质上不是某一道题的技巧,而是一套“在解空间树中搜索答案”的方法。只要题目要求“列出所有可能”“求所有方案”“尝试每一种选择”,你就该想到回溯。

⚡ 速记版模板#

// 模板用途:在“决策树”中枚举所有可能方案,典型如排列/组合/子集
void backtrack(...) {
    if (终止条件) { // 到达决策树叶子(或满足题目收集条件)
        收集答案; // 将当前路径/状态复制后加入结果集
        return; // 必须返回,避免继续向下搜索
    }
    for (每个选择) { // 枚举当前层所有可选分支
        做选择; // 修改路径与辅助状态(如 used、sum、标记数组)
        backtrack(...); // 进入下一层继续搜索
        撤销选择; // 回退现场,保证下一个分支在干净状态下运行
    }
}
java

🎯 什么时候想到回溯?#

典型关键词:

  • 全排列
  • 所有组合
  • 所有子集
  • 所有切分方案
  • 在网格中搜索路径
  • 需要枚举所有可行解

Hot 100 里的典型题目:

💡 回溯的本质#

回溯可以拆成三个动作:

  1. 做选择
  2. 递归进入下一层
  3. 撤销选择

这就是经典的:

选择 -> 递归 -> 回退

🚀 通用模板#

真正做题时,这个模板要根据题型微调。

🚀 模板一:子集型#

特点:

  • 每个元素“选或不选”
  • 每层从当前位置往后选
  • 结果通常在每一层都可以加入

🚀 模板二:组合型#

特点:

  • 只关心组合,不关心顺序
  • 用 startIndex 避免重复选择前面的元素

🚀 模板三:排列型#

特点:

  • 顺序不同算不同答案
  • 每层都要从头尝试
  • 通常需要 used[] 数组

🚀 模板四:网格搜索型#

例如单词搜索、岛屿搜索、路径搜索。

🧠 如何判断用哪个模板?#

  • 子集 / 组合:用 startIndex
  • 排列:用 used[]
  • 网格搜索:用方向数组 + 标记访问
  • 切分问题:枚举切分点,再判断当前片段是否合法

⚠️ 易错点#

  1. 忘记回退

    • 加入路径后一定要删掉
    • 标记访问后一定要恢复
  2. 组合和排列混淆

    • 组合:顺序不重要,用 startIndex
    • 排列:顺序重要,用 used[]
  3. 结果引用同一个对象

    • 加入答案时要 new ArrayList<>(path)
  4. 剪枝不到位

    • 组合总和这类题,不剪枝会慢很多

🎨 面试时怎么说#

你可以这样概括:

我把问题抽象成一棵决策树,每层枚举一种选择,走到底收集答案,返回时撤销当前选择,这就是回溯。

📌 一句话总结#

回溯的精髓不是递归,而是:

  • 把题目转成决策树
  • 明确每层做什么选择
  • 明确何时终止、何时剪枝、何时回退