leetCode 40.组合总和 II + 回溯算法 + 剪枝 + used数组 + 图解

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

(一)解法一:used数组

思路和分析: 本题和这道题的 leetCode 39.组合总和 + 回溯算法 + 剪枝区别是:

  • 不同点
  1. 本题candidates 中的每个数字在每个组合中只能使用 一次
  2. 本题 数组candidates 的元素是有重复的,而 leetCode 39.无重复元素的数组candidates
  • 相同点
  1. 解集不能包含重复的组合

总结 此题要求:元素在同一个组合内是可以重复的,多少次都可以,但两个组合不能相同。换句话说:" 集合(数组candidates )有重复 元素,但还不能有重复的组合 "

举个栗子:candidates = [1, 1, 2], target = 3 (注意前提candidates已经排序了)

思考(O_O)? 为啥used[i-1] == false 能表示同一树层candidates[i-1] "使用过" 这种情况呢?

  • 是因为在同一树层used[i-1] == false 能表示当前取的candidates[i] 是从**candidates[i-1]**回溯而来的
  • used[i]==true ,表示进入下一层递归 ,取下一个数,所以在树枝上

>>问题思考(O_O)?

1).什么是**"去重"**?

  • "去重" :就是使用过的元素不能重复选取

2).何为**"树枝去重"** 、"树层去重"(代码随想录Carl老师自创的名词)

可把组合问题 抽象为树形 结构,used"使用过" )在这个树形结构上是有两个维度的 ,一个维度表示是同一树枝上使用过,一个维度表示是同一树层上使用过

来看题目要求:"集合 (数组candidates有****重复 元素,但还不能****有重复的组合 "。

去重的是同一树层上的"使用过"是不同组合里的元素,而对于同一树枝上的都是一个组合里的元素,不用去重

  • 强调注意:在树层去重时,需要对数组排序

>>回溯三部曲:

1).确定回溯函数参数

  • path来收集符合条件的结果
  • result 保存 path,作为结果集
  • startIndex来控制for循环的起始位置
  • used 是bool型数组,用来记录同一树枝上的元素是否使用过
cpp 复制代码
vector<vector<int>> result;
vector<int>path;
void backtracking(vector<int>& candidates,int sum,int target,vector<bool>&used,int startIndex) 

2).递归的终止条件

  • sum > targetsum == targe
cpp 复制代码
if (sum > target) { // 这个条件其实可以省略
    return;
}
if (sum == target) {
    result.push_back(path);
    return;
}

================================================================================
可以写成这样:
在递归单层遍历的时候,会有剪枝的操作
for(int i=startIndex;i<candidates.size() && sum + candidates[i] <= target;i++) {
    ...
}

在这篇文章中有提到 for循环剪枝操作sum + candidates[i] <= target为「剪枝操作」。 感兴趣的小伙伴们可以看一下:leetCode 39.组合总和 + 回溯算法 + 剪枝https://blog.csdn.net/weixin_41987016/article/details/134672946?spm=1001.2014.3001.5501

3).单层搜索的逻辑

去重逻辑:if( i>0 &&**** candidates[i] == candidates[i - 1] && used[i - 1] == false),表示前一个树枝,使用了candidates[i - 1],也就是说同一树层使用过candidates[i - 1] 。那么此时 for循环 里通过 continue操作跳过此种情况的递归

cpp 复制代码
for(int i=startIndex;i<candidates.size() && sum + candidates[i] <= target;i++) {

    if(i>0 && candidates[i]==candidates[i-1] && used[i-1]==false) continue;

    ......
}

C++代码:

cpp 复制代码
class Solution {
public:
    vector<vector<int>> result;
    vector<int>path;
    void backtracking(vector<int>& candidates,int sum,int target,vector<bool>&used,int startIndex) {
        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]使用过
                要对同一树层使用过的元素进行跳过
            */
            if(i>0 && candidates[i]==candidates[i-1] && used[i-1]==false) continue;
            path.push_back(candidates[i]);
            sum+=candidates[i];
            used[i]=true;
            backtracking(candidates,sum,target,used,i+1);// 和39.组合总和的区别1,这里是i+1,每个数字在每个组合中只能使用一次
            used[i]=false;
            sum-=candidates[i];
            path.pop_back();
        }
    }
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        vector<bool> used(candidates.size(), false);
        // 首先把给candidates排序,让其相同的元素都挨在一起。
        sort(candidates.begin(),candidates.end());  
        backtracking(candidates,0,target,used,0);
        return result;
    }
};

(二)解法二:不用 used 数组,而用 startIndex来去重

cpp 复制代码
class Solution {
public:
    vector<vector<int>> result;
    vector<int>path;
    void backtracking(vector<int>& candidates,int sum,int target,int startIndex) {
        if(sum == target) {
            result.push_back(path);
            return;
        }
        for(int i=startIndex;i<candidates.size() && sum + candidates[i] <= target;i++) {
            if(i>startIndex && candidates[i]==candidates[i-1]) continue;
            path.push_back(candidates[i]);
            sum+=candidates[i];
            backtracking(candidates,sum,target,i+1);// 和39.组合总和的区别1,这里是i+1,每个数字在每个组合中只能使用一次
            sum-=candidates[i];
            path.pop_back();
        }
    }
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        vector<bool> used(candidates.size(), false);
        // 首先把给candidates排序,让其相同的元素都挨在一起。
        sort(candidates.begin(),candidates.end());  
        backtracking(candidates,0,target,0);
        return result;
    }
};

参考文章和推荐视频:

代码随想录 (programmercarl.com)https://www.programmercarl.com/0040.%E7%BB%84%E5%90%88%E6%80%BB%E5%92%8CII.html#%E6%80%9D%E8%B7%AF回溯算法中的去重,树层去重树枝去重,你弄清楚了没?| LeetCode:40.组合总和II_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV12V4y1V73A/?p=66&spm_id_from=pageDriver&vd_source=a934d7fc6f47698a29dac90a922ba5a3

相关推荐
业精于勤的牙6 分钟前
三角形最小路径和(二)
算法
风筝在晴天搁浅7 分钟前
hot100 239.滑动窗口最大值
数据结构·算法·leetcode
夏乌_Wx19 分钟前
练题100天——DAY31:相对名次+数组拆分+重塑矩阵
数据结构·算法
LYFlied19 分钟前
【算法解题模板】-解二叉树相关算法题的技巧
前端·数据结构·算法·leetcode
Ven%44 分钟前
【AI大模型算法工程师面试题解析与技术思考】
人工智能·python·算法
天勤量化大唯粉1 小时前
枢轴点反转策略在铜期货中的量化应用指南(附天勤量化代码)
ide·python·算法·机器学习·github·开源软件·程序员创富
爱学习的小仙女!1 小时前
算法效率的度量 时间复杂度 空间复杂度
数据结构·算法
AndrewHZ1 小时前
【复杂网络分析】什么是图神经网络?
人工智能·深度学习·神经网络·算法·图神经网络·复杂网络
Swizard1 小时前
拒绝“狗熊掰棒子”!用 EWC (Elastic Weight Consolidation) 彻底终结 AI 的灾难性遗忘
python·算法·ai·训练
fab 在逃TDPIE2 小时前
Sentaurus TCAD 仿真教程(十)
算法