一起养成写作习惯!这是我参与「掘金日新计划 · 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
}