每日一题- day 8

96 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 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. 关键字

搜索回溯、剪枝、组合总和