回溯是一种系统性的算法技术,用于查找计算问题的所有可能解决方案,特别是那些涉及约束满足的问题。本文档解释了回溯在LeetCode仓库中的实现和使用方法,包括常见模式、优化技术和示例。
有关动态规划等相关技术的更多信息,请参阅动态规划。有关滑动窗口和双指针,请参阅滑动窗口和双指针。
回溯是一种深度优先搜索方法,它逐步构建解决方案的候选,并在确定候选无法 dẫn đến 任何有效解决方案时放弃该候选(“回溯”)。
核心原则是通过以下方式探索所有可能的路径来找到解决方案:
回溯尤其适用于需要以下情况的问题:
来源:problems/46.permutations.md22-42 problems/78.subsets.md22-44
回溯方法通常遵循一个一致的模式,可以将其编码为模板。
回溯函数的核心模板通常包括:
来源:problems/46.permutations.md57-78 problems/78.subsets.md52-74 problems/40.combination-sum-ii.md48-103
回溯实现中的关键操作是:
| 操作 | 描述 | 常见实现 |
|---|---|---|
| 状态跟踪 | 跟踪正在构建的当前解决方案 | 通常通过引用传递的临时数组/列表 |
| 做出选择 | 将元素添加到当前解决方案路径 | tempList.push(element) |
| 递归 | 探索当前选择的后果 | backtrack(list, tempList, ...) |
| 回溯 | 撤销最近的选择 | tempList.pop() |
| 解决方案验证 | 检查当前状态是否为有效解决方案 | 通常是基本情况中的一个条件 |
| 结果收集 | 存储有效解决方案 | result.push([...tempList]) |
来源:problems/46.permutations.md60-68 problems/78.subsets.md58-64 problems/47.permutations-ii.md72-85
回溯算法探索一个状态空间,该空间表示为一个树,其中:
来源:problems/46.permutations.md37-47 problems/47.permutations-ii.md35-53
查找元素的所有可能排列,其中顺序很重要。
主要特性
实现细节
来源:problems/46.permutations.md57-78 problems/47.permutations-ii.md72-85
查找所有可能的组合或子集,其中顺序不重要。
主要特性
实现细节
来源:problems/78.subsets.md52-74 problems/90.subsets-ii.md58-73
查找元素组合以达到目标值的和。
主要特性
实现细节
来源:problems/39.combination-sum.md83-108 problems/40.combination-sum-ii.md116-130
高效的回溯实现可以避免探索无法 dẫn đến 有效解决方案的路径
重要的剪枝策略包括:
来源:problems/40.combination-sum-ii.md121-122 problems/47.permutations-ii.md76-78
在处理可能包含重复项的输入时,需要特殊处理以避免重复结果
最常见的重复项处理模式
if (i > start && nums[i] == nums[i-1]) continue;
来源:problems/47.permutations-ii.md50-53 problems/90.subsets-ii.md69
根据问题需求,回溯可以有不同的实现方式
| 变体 | 描述 | 用例 |
|---|---|---|
| 路径构建 | 维护当前路径并添加/删除元素 | 大多数回溯问题 |
| 基于索引 | 跟踪元素的索引而不是元素本身 | 带有重复项的排列 |
| Visited数组 | 布尔数组标记哪些元素正在使用 | 排列问题 |
| 提前终止 | 检查有效性条件以提前剪枝 | 基于和的问题 |
| 深拷贝 | 将当前路径的副本保存在结果列表中 | 所有回溯问题 |
来源:problems/46.permutations.md57-78 problems/47.permutations-ii.md72-85 problems/78.subsets.md52-74
该仓库包含许多使用回溯的问题。以下是分类:
排列问题:
子集/组合问题:
组合总和问题:
这些问题遵循核心的回溯模式,同时解决了不同的约束和要求。
来源:problems/47.permutations-ii.md159-169 problems/46.permutations.md153-163
回溯算法通常具有指数级时间复杂度,因为它们会探索所有可能的组合。对于大小为 n 的输入
像剪枝这样的优化技术对于使计算对于较大的输入保持可管理至关重要。
来源:problems/46.permutations.md147-151
在实现回溯时,请注意这些常见问题
来源:problems/47.permutations-ii.md47-53 problems/90.subsets-ii.md46-51