二十天刷题计划-- 组合总和 II

285 阅读1分钟

「这是我参与2022首次更文挑战的第29天,活动详情查看:2022首次更文挑战」。

1.题目

组合总和 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


思路

举例子:[1,2,2],选择前两个数,或者第一、三个数,都会得到相同的子集

一般像这种包含重复元素的,而题解答案中又要求去重的,通用套路就是先排序,排序后相同元素相邻可快速去重

前面我们提到:要去重的是“同一树层上的使用过”,如果判断同一树层上元素(相同的元素)是否使用过了呢。

如果candidates[i] == candidates[i - 1] 并且 used[i - 1] == false,就说明:前一个树枝,使用了candidates[i - 1],也就是说同一树层使用过candidates[i - 1]。

此时for循环里就应该做continue的操作。

递归过程如下:

1、遍历数组中的每一个数字。

2、递归枚举每一个数字可以选多少次,递归过程中维护一个target变量。如果当前数字小于等于target,我们就将其加入我们的路径数组path中,相应的target减去当前数字的值。也就是说,每选一个分支,就减去所选分支的值。

3、当target == 0时,表示该选择方案是合法的,记录该方案,将其加入res数组中。


代码

var combinationSum2 = function(candidates, target) {
    const res = []; path = [], len = candidates.length;
    candidates.sort();
    backtracking(0, 0);
    return res;
    function backtracking(sum, i) {
        if (sum > target) return;
        if (sum === target) {
            res.push(Array.from(path));
            return;
        }
        let f = -1;
        for(let j = i; j < len; j++) {
            const n = candidates[j];
            if(n > target - sum || n === f) continue;
            path.push(n);
            sum += n;
            f = n;
            backtracking(sum, j + 1);
            path.pop();
            sum -= n;
        }
    }
};