zl程序教程

您现在的位置是:首页 >  后端

当前栏目

118、【回溯算法】leetcode ——40. 组合总和 II:回溯法+剪枝优化(C++版本)

C++LeetCode算法 优化 版本 组合 II 40
2023-09-11 14:20:01 时间

题目描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
原题链接:40. 组合总和 II

解题思路

1、visited标记去重

本题的特点是,一个允许结果中出现相同数字,但每个元素仅能被选取一次。结果与结果之间不允许有重复,需要去重。
77. 组合(回溯法+剪枝优化) 的相同之处在于选取的元素都是只能被选一次,不同之处在于77里面没有重组元素。
39. 组合总和(剪枝优化) 的相同之处在于结果中可能会含有重复元素,但结果与结果之间不能重复。不同之处在于,元素可能被选多次。

  1. 元素只能被选一次:
    考虑了上述个相关题目的思路,依旧设置startIndex = i + 1控制起始位置,保证每个元素仅被选中一次。
  2. 结果集中去重:
    (1)若数组中元素都不相同,则根据startIndex = i + 1可知,每次选取的都为不相同元素,则不会出现重复结果集。
    (2)若数组中含有相同元素,则可能会出现重复的情况是之前已经某一相同元素与其余相同元素组合符合目标之和,当再选取此时的相同元素元素时,会与哪几个其余元素拼凑再一次导致出现重复结果。例如nums = [1, 1, 2], target = 3,则会出现两次选择[1, 2]
    因此,采取的措施是,当第一次遍历到含有相同元素的数时,正常遍历,当第二次遍历到上一次有重复元素的数,跳过该种情况即可。判定条件为i > 0 && candidates[i] == candidates[i - 1],但因为第一次遍历时,递归到下一个数时,也是会有1,但此时第一次遍历时还没结束,不能被跳出。因此需要再设置一个bool型向量visited判定上一个重复元素是否为本轮正被遍历元素,如果正被遍历元素,则继续正常向下遍历,直至完成该次遍历。如果不是,则说明是新一轮遍历,为了去重,直接跳过该次遍历。
class Solution {
public:
    vector<vector<int>> res;
    vector<int> index;    
    void backtracking(vector<int>& candidates, int target, int startIndex, vector<int> path, vector<bool> visited) {
        if(target == 0) {
            res.push_back(path);            
            return ;
        }
        for(int i = startIndex; i < candidates.size(); i++) {
            // 剪枝优化
            if(target - candidates[i] < 0)          return ;
            // i > 0 保证i-1不越界,candidates[i - 1] == candidates[i]判定是否前方有重复元素,visited[i - 1] == false判定为该元素的第一次正被遍历,还是已被遍历过,出现的新一轮遍历
            if(i > 0 && candidates[i - 1] == candidates[i] && visited[i - 1] == false) {             
                // 当该元素已被遍历过,此次为新一轮遍历时,则跳过该种情况
                continue;                
            }            
            visited[i] = true;
            path.push_back(candidates[i]);
            backtracking(candidates, target - candidates[i], i + 1, path, visited);            
            path.pop_back();            
            visited[i] = false;
        }
    }   
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        vector<int> path;
        vector<bool> visited(candidates.size(), false);
        // 先排序,再剪枝优化
        sort(candidates.begin(), candidates.end());
        backtracking(candidates, target, 0, path, visited);
        return res;
    }
};

2、startIndex判定是否去重

startIndex表示某次遍历的首个元素,根据istartIndex的关系大小,先去重会出现重复元素的结果,然后再让唯一情况加入结果集。

i == startIndex时,说明此时刚开始遍历某一数字。当i > startIndex时,说明已经遍历过前方的元素,正向后续遍历,若此时也出现candidates[i - 1] == candidates[i],则说明会出现重复情况,提前跳出该情况。

class Solution {
public:
    vector<vector<int>> res;
    vector<int> index;    
    void backtracking(vector<int>& candidates, int target, int startIndex, vector<int> path) {
        if(target == 0) {
            res.push_back(path);            
            return ;
        }
        for(int i = startIndex; i < candidates.size(); i++) {
            if(target - candidates[i] < 0)          return ;
            if(i > startIndex && candidates[i - 1] == candidates[i]) {                
                continue;                
            }            
            path.push_back(candidates[i]);
            backtracking(candidates, target - candidates[i], i + 1, path);            
            path.pop_back();            
        }
    }   
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        vector<int> path;
        sort(candidates.begin(), candidates.end());
        backtracking(candidates, target, 0, path);
        return res;
    }
};

原题链接:40.组合总和II