回溯算法篇--组合总和 II

96 阅读2分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第9天,点击查看活动详情

1.题目一

40. 组合总和 II

给定一个候选人编号的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的每个数字在每个组合中只能使用 一次 。

注意:解集不能包含重复的组合。 

示例 1:

输入: candidates = [10,1,2,7,6,1,5], target = 8,

输出: [ [1,1,6], [1,2,5], [1,7], [2,6] ]

示例 2:

输入: candidates = [2,5,2,1,2], target = 5,

输出: [ [1,2,2], [5] ]  

提示:

1 <= candidates.length <= 100

1 <= candidates[i] <= 50

1 <= target <= 30


思路 每次都在做选择,选一个数,然后选下一个,看看当前选择受到哪些限制,去做剪枝:

选过的不能再选 不能产生相同的组合 当和为 target,找到一个正确的解,加入解集。当和 > target,爆掉了,不用继续选了,结束当前递归,继续搜别的分支,找齐所有的解。 因此,回到上一个节点,撤销当前选择的数字,去进入下一个分支。

回溯法三部曲

  • 递归函数的返回值以及参数
  • 回溯函数终止条件
  • 单层搜索的过程

都知道组合问题可以抽象为树形结构,那么“使用过”在这个树形结构上是有两个维度的,一个维度是同一树枝上使用过,一个维度是同一树层上使用过。

我们可以画一颗二叉树来理解,如[1,1,3]如继续选相同的元素的话,这颗子树会在选第一个1的时候构建出一颗二叉树,然后假如此时再选第二个1的话会与的一颗子树当中的1-3重复,所以此时跳过。


代码

var combinationSum2 = function (candidates, target) {
    let res = []
    let track = []
    const backtrack = (start, sum) => {
        if (sum === target) {
            return res.push(track.slice())
        }
        if (sum > target) {
            return
        }
        for (let i = start; i < candidates.length; i++) {
            if (i - 1 >= start && candidates[i - 1] == candidates[i]) {
                continue
            }
            //  选择
            track.push(candidates[i])
            sum += candidates[i]
            //  递归
            backtrack(i + 1, sum)
            //  撤销选择
            track.pop()
            sum -= candidates[i]
        }
    }
    candidates.sort((a, b) => a - b)
    backtrack(0, 0)
    return res
}