题目简述:从候选的数字里面选出的数字之和 = target 的所有组合
就像我在46题里面的作答一样,一般的框架就是修改start来达到剪枝的目的
这里的这个写法不能ac,主要是数组没有sort的问题
var combinationSum = function (candidates, target) {
let res = []
var dfs = function (start, one, sum) {
if (sum === target) {
res.push([...one])
return
}
for (let i = start; i < candidates.length; ++i) {
// 不满足条件,剪枝
if (sum + candidates[i] > target) {
return
}
sum += candidates[i]
one.push(candidates[i])
// 因为能重复使用元素,那么就可以直接从 i 直接开始
// 如果不能重复使用元素,就要 i + 1 了
dfs(i, one, sum)
// 回溯
sum -= candidates[i]
one.pop()
}
}
dfs(0, [], 0)
return res
};
下面这种写法比较清爽一丢
var combinationSum = function (candidates, target) {
let res = []
var dfs = function (start, one, sum) {
if (sum === 0) {
res.push([...one])
return
}
if (sum < 0) return
for (let i = start; i < candidates.length; ++i) {
one.push(candidates[i])
dfs(i, one, sum - candidates[i])
one.pop()
}
}
dfs(0, [], target)
return res
};
上面两种解法的结构差不多,但是第一种需要排序,后面一种不需要,造成这个差异的原因是,第一种的剪枝判断是
if (sum + candidates[i] > target) {
return;
}
这种写法的前提是:candidates 已经是升序排列的。否则你无法确保“后面值更大”,从而导致错误地提前剪掉合法路径。
而后面一种是
if (sum < 0) return;
这种会尝试每一种情况。
总结
- 回溯模板可以灵活调整:判断条件放在前 or 后,是否传递状态变量如
sum,是否排序提前剪枝等; - LeetCode 39 的关键点在于重复使用元素,所以递归时传入的仍然是当前下标
i而不是i + 1;