开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 1 天,点击查看活动详情
1. 今日语录
勇于尝试,敢于试错。
2. 题目
组合总和 II
给定一个候选人编号的集合 candidates 和一个目标数 target , 找出 candidates 中所有可以使数字和为 target 的组合。 candidates 中的每个数字在每个组合中只能使用 一次 。 注意:解集不能包含重复的组合。
3. 思路
与 day006_组合总和:每个数字可以被重复选取 不同的是 这题每个数字只能使用一次 所以 选择数字的开始索引inx 在递归函数中需 改为 inx +1
结合之前的思路,整个流程就像一棵树,可以剪枝优化 在搜索回溯的过程中,sum > target的情况可能会比较多,这就会导致过多不必要的递归。 如果我们在组合前对数组进行升序排列,前一个较小数字已经超了的话, 后面就不需要再尝试了,可以提前终止递归,减少时间复杂度。
hhh,改成 inx + 1 后,新的问题诞生了。
问题1:candidates数组中是有重复元素的
方案1:
if (i >= 1 && candidates[i] === candidates[i - 1]) {
continue;
}
当我自信满满以为万事大吉后,bug又来了。
问题2: 以 candidates = [10, 1, 2, 7, 6, 1, 5] target = 8为例 组合 [1,1,6] 没有被统计(代码排除了所有重复元素)。
方案2: 梳理一下,题目本身需要满足以下两个条件。 (1)每一次组合是支持重复元素(重复因素是指位于数组不同位置,但是数字本身值是一样的)的。 (2)开始索引 inx 的 值 必须是 唯一的
if (i > inx && candidates[i] === candidates[i - 1]) {
continue;
}
方案2,看似简单,其实还真不好想。 i > inx 说明 至少循环了一次 所以 candidates[i - 1]肯定不会越界
注意!每一种组合的 开始索引inx 是保持不变的, 所以 i > inx 又说明 流程已经进入下一轮组合
这个时候,既满足 每一轮组合内无需去重,每一轮组内的 开始索引inx 是唯一的。
4. 代码
var combinationSum2 = function (candidates, target) {
let res = []
candidates.sort((a, b) => a - b)
const dfs = (sum, path, inx) => {
if (sum === target) {
res.push(Array.from(path))
return
}
for (let i = inx; i < candidates.length; i++) {
if (i > inx && candidates[i] === candidates[i - 1]) {
continue;
}
if (sum > target) {
break;
}
path.push(candidates[i]);
sum += candidates[i];
dfs(sum, path, i + 1)
path.pop();
sum -= candidates[i];
}
}
dfs(0, [], 0)
return res;
};
(function () {
const candidates = [10, 1, 2, 7, 6, 1, 5];
const target = 8;
console.log('组合总和', combinationSum2(candidates, target));
})()
5. 关键字
搜索回溯、剪枝、组合总和