【Leetcode】39. 组合总和

84 阅读1分钟

leetcode-39.png

题目简述:从候选的数字里面选出的数字之和 = 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