#39
中等
中频
回溯组合总和
这是一道围绕数组展开的高频练习。建议先掌握「回溯」这套写法,再结合下方步骤讲解理解状态维护、边界处理和复杂度取舍。
数组
题目分析
这道题表面上是在处理「组合总和」,但先要想清楚题目到底让你返回什么,以及过程中哪些约束必须一直满足。从题型上看,它主要在考 数组 这些能力。这类题更像在一棵决策树里向下展开,关键是递归时带什么状态、什么时候回退。只有先把题目要求翻译成人话,后面的推荐代码才是在实现思路,而不是直接给答案。
接下来怎么看推荐代码: 带着这个理解再看推荐代码时,重点观察这条主线:回溯枚举当前选哪个数字,允许重复使用同一个候选值,因此递归下一层时索引可以不变。
推荐代码
推荐解法:回溯
时间复杂度: O(组合数)
空间复杂度: O(target)
核心思路: 回溯枚举当前选哪个数字,允许重复使用同一个候选值,因此递归下一层时索引可以不变。
import java.util.ArrayList;
import java.util.List;
class Solution {
/**
* 找出所有可以使数字和为目标值的组合
*
* @param candidates 候选数字数组
* @param target 目标值
* @return 所有可能的组合
*/
public List<List<Integer>> combinationSum(int[] candidates, int target) {
List<List<Integer>> ans = new ArrayList<List<Integer>>();
List<Integer> combine = new ArrayList<Integer>();
dfs(candidates, target, ans, combine, 0);
return ans;
}
/**
* 深度优先搜索函数
*
* @param candidates 候选数字数组
* @param target 当前目标值
* @param ans 结果集合
* @param combine 当前组合
* @param idx 当前索引
*/
public void dfs(int[] candidates, int target, List<List<Integer>> ans, List<Integer> combine, int idx) {
// 当索引超出范围时,返回
if (idx == candidates.length) {
return;
}
// 当目标值为0时,找到有效组合
if (target == 0) {
ans.add(new ArrayList<Integer>(combine)); // 深拷贝当前组合
return;
}
// 跳过当前数,递归下一个索引
dfs(candidates, target, ans, combine, idx + 1);
// 选择当前数(如果不大于目标值)
if (target - candidates[idx] >= 0) {
combine.add(candidates[idx]);
// 递归搜索,索引不变(可以重复使用当前数)
dfs(candidates, target - candidates[idx], ans, combine, idx);
// 回溯,移除最后添加的数
combine.remove(combine.size() - 1);
}
}
}结构化讲解
面试时怎么讲
开场思路
这题我会用回溯,核心是用 start 下标避免重复、用 remain 做剪枝。
核心过程
- 路径里存当前已选数字。
- 每层从 start 开始枚举可选数字。
- 选中某个数字后,因为允许重复,所以递归仍从当前索引开始。
- 当剩余值为 0 时收集答案,剩余值小于 0 时剪枝。
复杂度总结
时间复杂度取决于解空间大小,空间复杂度主要是递归深度。
面试补一句:这题常见追问是:为什么下一层还是从 i 而不是 i+1。
核心思路
组合总和的关键是“组合”和“可重复使用”。组合意味着顺序不重要,所以用起始下标避免重复;可重复意味着选中当前数后,下一层仍可继续选它。
步骤讲解
1
用剩余目标值驱动搜索
递归中维护还差多少才能凑到 target。
为什么这样做:剩余值为 0 时当前路径就是一组合法答案。
对应代码提示:dfs(start, remain, path);
2
选择当前数后允许继续选它
加入 candidates[i] 后,下一层仍从 i 开始递归。
为什么这样做:题目允许同一个数被重复使用。
对应代码提示:dfs(i, remain - candidates[i], path);
3
剩余值小于 0 时立即剪枝
一旦剩余目标变成负数,当前路径不可能再成功。
为什么这样做:后续继续加正数只会更偏离目标。
对应代码提示:if (remain < 0) return;
易错点
下一层从 i+1 开始
这样会错误禁止同一数字重复使用。
正确理解:允许重复时,递归下一层仍从当前索引
i 开始。没有用 start 控制顺序
同一组合会以不同排列顺序重复出现。
正确理解:每层只枚举当前及其后面的候选值。
remain 为负还继续搜
纯浪费分支,不会产生合法答案。
正确理解:剩余值小于 0 立即返回。
复杂度与适用判断
时间复杂度:O(组合数)
空间复杂度:O(target)
比其他方案更好在哪里:比暴力枚举所有加法表达式更直接,也更容易做剪枝。
适用判断:组合题如果元素可重复使用,通常就是“start 不变”的回溯模板。
额外提醒
- 组合去重靠 start,下层能否重用靠递归传参是否保留 i。
动画演示
如果你还没完全看懂,可以展开动画演示。
回溯动画演示
想看这段解法是怎么一步步运行的,可以展开动画继续观察。
回溯动画演示
想看这段解法是怎么一步步运行的,可以展开动画继续观察。
准备就绪 - 输入候选数组和目标值,然后点击开始
候选数组:
当前组合:
空
和: 0 递归栈:
栈空
找到的组合 (0 个):
暂无结果
步骤: 0 | 状态: initial | 当前索引: 0 | 候选元素: 无
动画演示仅在桌面端提供,移动端请优先阅读推荐代码与结构化讲解。