LeetCode40. 组合总和 II

40. 组合总和 II

给定一个候选人编号的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。candidates 中的每个数字在每个组合中只能使用 一次 。(坑:每个数字是指每个下标位置上的数字,这会产生重复)
注意:解集不能包含重复的组合。
重复集合不可重复选取每个位置上的数字(深度去重中的递归去重)
重复集合不可返回重复的组合答案(宽度去重中的特判)

深度去重:
1.递归去重:(去重复下标,不在同一下标取值)任意集合只要不可重复选取当前位置元素时就要应用,但并不保证是否组合有重复元素,因为集合可能本身含重复值,体现在

startindex=i+1

2.特判去重(去重复值,哪怕是来自不同下标):必须是含重复值的集合且该集合要求返回的组合里不含有重复元素,体现在基于1的情况下额外特判:

1.if (i == startIndex && candidates[i] == candidates[i - 1])continue;
2.if (i > 0 && used[i - 1] == true && candidates[i] == candidates[i - 1] ) continue;

宽度去重:
1.特判去重:(去除重复组合,哪怕来自不同下标),必须是含重复值的集合并且该集合要求返回的组合间不能相同.

1.if (i != startIndex && candidates[i] == candidates[i - 1])continue;
2.if (i > 0 && used[i - 1] == false && candidates[i] == candidates[i - 1] ) continue;
3.unordered_set uset;if (uset.find(nums[i]) != uset.end()) continue;

示例 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 <= 3

startindex和used数组都是大同小异,来判定当前相同的节点是否在树层上(即第一个树枝上)
使用startIndex去重的逻辑:是否自己在树层上(第一个树枝上),如果是,则去掉。条件大于设置为i != index更容易理解

class Solution {
private:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(vector<int>& candidates, int target, int sum, int startIndex) {
        if (sum == target) {//递归的深度取决于目标值,组合的长度是任意的.
            result.push_back(path);
            return;
        }
        for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++) {
            // 树层宽度去重就是for去重(重复的集合要产生不可重复的组合时应用)
            // 宽度层的startindex都小于当前i(不等于),深度层的startindex都等于当前i
            if (i != startIndex && candidates[i] == candidates[i - 1]) {
                continue;
                //宽度去重的原因:会导致出现多个相同组合
                //{1,1,2,3}第二个1能取到的组合,第一个1也能取到
                //"第二个1的第一轮选第二个1,还剩下{2,3}"与"第一个1第一轮选第一个1,还剩下{2,3}"产生的后续组合是一个效果,即第二个1产生的结果第一个1有,不能产生的结果第一个1还有.
            }
            sum += candidates[i];
            path.push_back(candidates[i]);
            //树枝深度去重之一就是递归去重(任意集合只要不可重复选取当前位置元素时应用)
            //还有一种深度去重就是i==startindex,用于重复集合里不可重复选取元素.
            backtracking(candidates, target, sum, i + 1); //深度-递归函数去重:不可重复选取当前下标,i后移
            sum -= candidates[i];
            path.pop_back();
        }
    }

public:
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        path.clear();result.clear();
        // 排序可以让相同的元素都挨在一起。
        sort(candidates.begin(), candidates.end());
        backtracking(candidates, target, 0, 0);
        return result;
    }
};

使用used去重的逻辑:遍历到当前节点时,若前面一个值与我相等,判定是否还在同一个树枝上,(在的话说明自己初于递归层(深度层,树枝)中)如果不是,则证明自己在树层上,满足树层去重

class Solution {
private:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(vector<int>& candidates, int target, int sum, int startIndex, vector<bool>& used) {
        if (sum == target) {
            result.push_back(path);
            return;
        }
        for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++) {
            // used[i - 1] == true,说明同一树枝candidates[i - 1]使用过,或者说明前一个元素正处于递归层(深度层),当前元素是递归过程中的产物,跳过属于重复集合里的深度去重,即不产生内含相同值的组合
            // used[i - 1] == false,说明同一树层candidates[i - 1]使用过,或者说明前一个元素没有处于递归层,即当前元素是for过程(宽度层)的产物,跳过属于重复集合里的宽度去重,即不产生重复组合
            // 要对同一树层使用过的元素进行跳过
            if (i > 0 && candidates[i] == candidates[i - 1] && used[i - 1] == false) {
                continue;
            }
            sum += candidates[i];
            path.push_back(candidates[i]);
            used[i] = true;
            backtracking(candidates, target, sum, i + 1, used); 
            used[i] = false;
            sum -= candidates[i];
            path.pop_back();
        }
    }

public:
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        path.clear();result.clear();
        sort(candidates.begin(), candidates.end());
        vector<bool> used(candidates.size(), false);
        backtracking(candidates, target, 0, 0, used);
        return result;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值