力扣LeetCode: 40.组合总和Ⅱ

 题目:

        给定一个候选人编号的集合 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

解法一:暴力枚举

        我们在这种数组的子数组中求和的问题,很自然想到暴力枚举的方法。观察题目条件可知,题目的限制条件范围较小。我们不妨试试暴力枚举的方法。

        combinationSum2类用来接收一个整数数组和一个目标和,返回所有可能的组合。在其中呢,我们创建一个result空数组用来接收数据。其次对于给定的一维数组进行排序。便于后面减少不必要的计算。combination数组用来构建当前的组合。然后利用backtrack_bruteforce类生成所有可能的组合。

        backtrack_bruteforce类,首先进行了判断:若是剩余和为0的话,自然返回当前构建数组即可。

  1. 终止条件:如果remain等于0,说明当前组合的和正好等于目标和,因此将当前组合添加到结果中。
  2. 循环遍历:从start索引开始遍历候选数组。
  3. 去重处理:如果当前数字和前一个数字相同,并且前一个数字在当前分支中还没有被使用过(即i > start),则跳过当前数字。这是为了避免生成重复的组合。
  4. 选择:将当前数字添加到组合中。
  5. 递归调用:递归地调用backtrack_bruteforce函数,更新剩余和(remain - candidates[i])和起始索引(i + 1),以便在下一次递归中使用下一个数字。
  6. 回溯:在递归调用返回后,撤销对当前数字的选择,以便尝试其他可能的组合。

        通过这种方式,backtrack_bruteforce函数能够生成所有可能的组合,这些组合的数字和正好等于目标和,并且没有重复的组合。


class Solution {
public:
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        vector<vector<int>> result;
        sort(candidates.begin(), candidates.end()); // 先排序以便后续去重
        vector<int> combination;
        backtrack_bruteforce(candidates, target, 0, combination, result);
        return result;
    }

private:
    void backtrack_bruteforce(vector<int>& candidates, int remain, int start, vector<int>& combination, vector<vector<int>>& result) {
        if (remain == 0) {
            result.push_back(combination);
            return;
        }
        for (int i = start; i < candidates.size(); ++i) {
            // 去重处理:如果当前数字和前一个数字相同,并且前一个数字没有被使用,则跳过当前数字
            if (i > start && candidates[i] == candidates[i - 1]) continue;
          
            combination.push_back(candidates[i]);
            backtrack_bruteforce(candidates, remain - candidates[i], i + 1, combination, result);
            combination.pop_back();
        }
    }
};

        在提交之后,发现暴力枚举还是太耗时间。这时候有人就要问了,老师,老师,这种解决办法还是太吃时间了,有没有快一点的呢?有点兄弟,有的!

解法二:剪枝搜索

        核心思想还是暴力枚举,只不过在遍历途中对于一些已经不符合要求的进行去除。在这道题的具体实现中,我们已经排序了,所以若是左节点已经大于等于结果了,那么他的右节点就不用进行判断了,因为右节点一定比左节点大,所以肯定溢出了。


class Solution {
public:
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        vector<vector<int>> result;
        sort(candidates.begin(), candidates.end()); // 先排序以便后续剪枝和去重
        vector<int> combination;
        backtrack_pruned(candidates, target, 0, combination, result);
        return result;
    }

private:
    void backtrack_pruned(vector<int>& candidates, int remain, int start, vector<int>& combination, vector<vector<int>>& result) {
        if (remain == 0) {
            result.push_back(combination);
            return;
        }
        for (int i = start; i < candidates.size(); ++i) {
            // 去重处理:如果当前数字和前一个数字相同,并且前一个数字没有被使用,则跳过当前数字
            if (i > start && candidates[i] == candidates[i - 1]) continue;
            //if (candidates[i] > remain) break; // 剪枝:如果当前数字已经大于剩余和,则无需继续
            combination.push_back(candidates[i]);
            backtrack_pruned(candidates, remain - candidates[i], i + 1, combination, result); // 注意这里是 i + 1,每个数字只能用一次
            combination.pop_back();
        }
    }
};

        此时,我们再次提交。通过啦!!!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值