当前:LC39 · 组合总和 · 能量熔炉回溯 · 首次出现于 Day 27 · 路径:顶栏「56天打卡」→ 点击 LC 题号 → 逐题动画

LC39算法模式回溯 · 组合总和选择树与撤销绳

组合总和 · 能量熔炉回溯

candidates=[2,3,6,7], target=7,找所有和为 7 的不重复配方(可重复选)。

题目是什么

candidates=[2,3,6,7], target=7,找所有和为 7 的不重复配方(可重复选)。

解决什么问题

当前路径、可选范围和终止条件分别是什么?

核心结论

能量熔炉需要填满 target 点能量。材料卡片 candidates 库存无限,可重复投入。我们要找所有「不重复配方」——[2,2,3] 与 [7] 合法,但 [2,3,2] 与 [2,2,3] 是同一组合。

01交互算法精讲

先说结论:这道题到底解决什么

怎样从“candidates=[2,3,6,7], target=7,找所有和为 7 的不重复配方(可重复选)。”推导出 回溯 · 组合总和,并证明每次状态变化都不会漏掉答案?

中心结论:能量熔炉需要填满 target 点能量。材料卡片 candidates 库存无限,可重复投入。我们要找所有「不重复配方」——[2,2,3] 与 [7] 合法,但 [2,3,2] 与 [2,2,3] 是同一组合。

读完必须能回答
  1. 1.暴力方案在哪里重复计算,为什么仍然是正确基线?
  2. 2.当前路径、可选范围和终止条件分别是什么?
  3. 3.不变量“path 和单调增,避免 [2,3] 与 [3,2] 重复。”为什么能保证算法安全前进?
02交互算法精讲

完整题目与题意拆解

给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的数字可以无限制重复被选取。

在本站主例中,candidates=[2,3,6,7],target=7,找所有和为 7 的组合(可重复)。

算法最终需要得到或观察:组合 [2,2,3] 与 [7]。

把题目翻译成状态
  • 输入:candidates=[2,3,6,7], target=7,找所有和为 7 的不重复配方(可重复选)。
  • 机器需要维护:路径 path、剩余 target、start 下标。
  • 最终可观察结果:合法配方 [[2,2,3], [7]]。
动画 1 · 题意扫描

先看清算法到底要维护什么

先建立输入、目标、输出和第一批状态,不急着进入模板。

Step 1/20%
candidates=[2,3,6,7], target=7combinationSum(candidates, target)
正在加载 LC39 能量熔炉...
03交互算法精讲

第一层方案:暴力做法

先生成全部结果再过滤会探索大量无效分支;回溯在选择阶段剪枝,并在返回时恢复现场。

暴力方案的价值是确认题意并提供正确性基线。它通常会覆盖所有候选, 但没有保存已经确认的信息,因此同一状态会被重新计算。

动画 2 · 暴力重复

重复工作究竟发生在哪里

把重复读取或重复搜索的区域明确标出,再决定优化必须保存什么。

Step 1/20%
先做对:建立暴力基线枚举所有候选并完整验证
正在加载 LC39 能量熔炉...
优化方向:能量熔炉需要填满 target 点能量。材料卡片 candidates 库存无限,可重复投入。我们要找所有「不重复配方」——[2,2,3] 与 [7] 合法,但 [2,3,2] 与 [2,2,3] 是同一组合。
04交互算法精讲

整体地图:先做什么,再做什么

  1. 1建模把输入翻译成“选择树与撤销绳”,明确答案需要观察什么。
  2. 2状态只维护 路径 path、剩余 target、start 下标。
  3. 3转移每一步按照 能量熔炉需要填满 target 点能量。材料卡片 candidates 库存无限,可重复投入。我们要找所有「不重复配方」——[2,2,3] 与 [7] 合法,但 [2,3,2] 与 [2,2,3] 是同一组合。
  4. 4收尾读取 合法配方 [[2,2,3], [7]]。,并复核边界与复杂度。
05交互算法精讲

选择树与撤销绳:核心概念

路径记录已做选择,候选集合决定下一步,撤销保证兄弟分支互不污染。

这道题不是为了记住一个组件名称,而是为了让状态具有可解释的语义:路径 path、剩余 target、start 下标。

核心不变量
  • path 和单调增,避免 [2,3] 与 [3,2] 重复。
动画 3 · 核心概念

建立“选择树与撤销绳”心智模型

用主例建立核心状态,先预测下一步,再公开正确分支和理由。

Step 1/40%
candidates=[2,3,6,7], target=7combinationSum(candidates, target)
正在加载 LC39 能量熔炉...
06交互算法精讲

核心机制:状态如何一步步变化

题目要求出总和为 sum 的所有组合,组合需要去重。 这一题和第 47 题类似,只不过元素可以反复使用。

回溯:排序后选 start,递归选≥当前数,和==target 记录;和>target 剪枝。

执行过程中持续维护:路径 path、剩余 target、start 下标。

正确性依赖以下不变量:path 和单调增,避免 [2,3] 与 [3,2] 重复。

面试时可以压缩为:排序+start 控制重复:递归可重复选同一数,sum>target 剪枝,O(2^n) 上界。

落到当前题,执行机制可以压缩为:能量熔炉需要填满 target 点能量。材料卡片 candidates 库存无限,可重复投入。我们要找所有「不重复配方」——[2,2,3] 与 [7] 合法,但 [2,3,2] 与 [2,2,3] 是同一组合。每一次更新都必须保持核心不变量,而不是只让样例碰巧得到正确答案。

动画 4 · 机制构建

一次状态转移为什么成立

集中观察一次选择、计算和状态写回,让变量变化与原因同时出现。

Step 1/50%
选 2, remain→5path.append(2); dfs(0, 5)
正在加载 LC39 能量熔炉...
07交互算法精讲

正确性证明:为什么不会漏答案

初始化

初始化:算法开始时,全部合法候选仍在状态表示范围内;path 和单调增,避免 [2,3] 与 [3,2] 重复。

保持

保持:执行“能量熔炉需要填满 target 点能量。材料卡片 candidates 库存无限,可重复投入。我们要找所有「不重复配方」——[2,2,3] 与 [7] 合法,但 [2,3,2] 与 [2,2,3] 是同一组合。”时,只删除已经能证明不可能的候选,并把新信息写回 路径 path、剩余 target、start 下标。

终止

终止:没有待处理状态或达到命中条件时,当前可观察结果就是“合法配方 [[2,2,3], [7]]。”。

正确性抓手不是“样例跑通”,而是每一帧结束后仍能复述:path 和单调增,避免 [2,3] 与 [3,2] 重复。
08交互算法精讲

完整执行过程

  1. 1candidates=[2,3,6,7], target=7第一屏应一眼看懂:找所有不重复配方。 因为:LC39 是组合总和,材料可重复使用。
  2. 2start 闸门演示 start=1用户能直观看懂 start 如何避免 [2,3] 与 [3,2]。 因为:start 不是普通下标,它是一道禁止回头的闸门。
  3. 3再选 2, remain→3演示可重复选同一数字。 因为:传 i 而非 i+1 是 LC39 与 LC40 的关键区别。
  4. 4path = path[:len-1]从死路退回上一层。 因为:回溯不是瞎试,而是选择 → 递归 → 撤销选择 → 换下一个选择。
  5. 5回溯到 path=[2]继续探索 [2] 的其他分支。 因为:回溯后继续 for 循环下一个 i。
  6. 6根层选 3探索以 3 开头的配方。 因为:根层 i=1 选 3,下一层 start=1。
  7. 7收集 [7]第二个完整配方。 因为:单材料恰好等于 target。
  8. 8回溯模板总结串联全部关键概念。 因为:排序 + start 闸门防排列重复;递归传 dfs(i, remain-candidate) 允许重复选当前数;remain==0 收集,candidate>remain 剪枝。搜索树规模最坏指数级,递归深度约 target/minCandidate。
动画 5 · 完整执行

从输入完整走到可观察结果

从主例第一帧运行到答案,时间轴始终显示当前状态、因果解释和下一步。

Step 1/80%
candidates=[2,3,6,7], target=7combinationSum(candidates, target)
正在加载 LC39 能量熔炉...
09交互算法精讲

把动画和 Go 代码逐行对应

代码窗口不会按容易漂移的固定数字行号硬绑动画,而是根据当前语义阶段, 在完整 Go 实现中定位选择、计算、写回或返回分支。当前变量与高亮行一起变化。

动画 6 · 代码映射

让每个动作都落到 Go 分支

重新执行关键分支,只显示当前代码附近窗口,并说明这行为什么在此刻运行。

Step 1/60%
start 闸门演示 start=1for i := start
正在加载 LC39 能量熔炉...
10交互算法精讲

完整 Go 提交代码与最小测试

完整 Go 解法
import "sort"

func combinationSum(candidates []int, target int) [][]int {
	if len(candidates) == 0 {
		return [][]int{}
	}
	c, res := []int{}, [][]int{}
	sort.Ints(candidates)
	findcombinationSum(candidates, target, 0, c, &res)
	return res
}

func findcombinationSum(nums []int, target, index int, c []int, res *[][]int) {
	if target <= 0 {
		if target == 0 {
			b := make([]int, len(c))
			copy(b, c)
			*res = append(*res, b)
		}
		return
	}
	for i := index; i < len(nums); i++ {
		if nums[i] > target { // 这里可以剪枝优化
			break
		}
		c = append(c, nums[i])
		findcombinationSum(nums, target-nums[i], i, c, res) // 注意这里迭代的时候 index 依旧不变,因为一个元素可以取多次
		c = c[:len(c)-1]
	}
}
最小测试集合
func main() {
    // 1. 主例
    //    输入:mode="combination-sum", nums=[2,3,6,7], target=7
    //    期望:组合 [2,2,3] 与 [7]。
    //
    // 2. 失败 / 未命中
    //    检查:不排序 start 会重复组合。
    //
    // 3. 边界
    //    空输入、单元素、最小合法规模,以及答案恰好落在边界的情况。
    //
    // 4. 迁移
    //    LC40 组合总和 II;LC46 全排列
}

Go 参考实现基于 halfrost/LeetCode-Go 的 MIT 许可代码整理,并按本站教学结构补充解释与动画映射。

11交互算法精讲

正确性与复杂度

时间复杂度 搜索树规模,最坏指数级

执行过程中只保留仍可能影响答案的状态。能量熔炉需要填满 target 点能量。材料卡片 candidates 库存无限,可重复投入。我们要找所有「不重复配方」——[2,2,3] 与 [7] 合法,但 [2,3,2] 与 [2,2,3] 是同一组合。

空间复杂度 递归深度约 target/minCandidate,不计答案空间

额外状态主要用于维护:路径 path、剩余 target、start 下标。

终局不变量
  • path 和单调增,避免 [2,3] 与 [3,2] 重复。
12交互算法精讲

最容易写错的地方

错误 1

误写 dfs(i+1, ...) —— LC39 可重复选,必须传 i。

错误 2

不设 start —— 会产生 [2,3] 与 [3,2] 排列重复。

错误 3

不排序 —— 无法用 break 剪枝 candidates[i] > remain。

边界复查

必须额外检查空输入、单元素、未命中或不可达情况,以及恰好落在边界的输入。

13交互算法精讲

最后复盘:带走逻辑链

  1. 1题意candidates=[2,3,6,7], target=7,找所有和为 7 的不重复配方(可重复选)。
  2. 2重复先生成全部结果再过滤会探索大量无效分支;回溯在选择阶段剪枝,并在返回时恢复现场。
  3. 3优化能量熔炉需要填满 target 点能量。材料卡片 candidates 库存无限,可重复投入。我们要找所有「不重复配方」——[2,2,3] 与 [7] 合法,但 [2,3,2] 与 [2,2,3] 是同一组合。
  4. 4证明path 和单调增,避免 [2,3] 与 [3,2] 重复。
  5. 5复杂度时间 搜索树规模,最坏指数级,空间 递归深度约 target/minCandidate,不计答案空间
面试表达:排序 + start 闸门防排列重复;递归传 dfs(i, remain-candidate) 允许重复选当前数;remain==0 收集,candidate>remain 剪枝。搜索树规模最坏指数级,递归深度约 target/minCandidate。
迁移练习
  • LC40 组合总和 II
  • LC46 全排列