本文 https://github.com/youngyangyang04/leetcode-master 已经收录,里面还有leetcode刷题攻略、各个类型经典题目刷题顺序、思维导图,可以fork到自己仓库,有空看一看一定会有所收获,如果对你有帮助也给一个star支持一下吧!

40.组合总和II

题目链接:https://leetcode-cn.com/problems/combination-sum-ii/

给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的每个数字在每个组合中只能使用一次。

说明:
所有数字(包括目标数)都是正整数。
解集不能包含重复的组合。

示例 1:
输入: candidates = [10,1,2,7,6,1,5], target = 8,
所求解集为:
[
[1, 7],
[1, 2, 5],
[2, 6],
[1, 1, 6]
]

示例 2:
输入: candidates = [2,5,2,1,2], target = 5,
所求解集为:
[
[1,2,2],
[5]
]

思路

这道题目和39.组合总和如下区别:

  1. 本题candidates 中的每个数字在每个组合中只能使用一次。
  2. 本题数组candidates的元素是有重复的,而39.组合总和是无重复元素的数组candidates

最后本题和39.组合总和要求一样,解集不能包含重复的组合。

本题的难点在于区别2中:集合(数组candidates)有重复元素,但还不能有重复的组合

一些同学可能想了:我把所有组合求出来,再用set或者map去重,这么做很容易超时!

所以要在搜索的过程中就去掉重复组合。

很多同学在去重的问题上想不明白,其实很多题解也没有讲清楚,反正代码是能过的,感觉是那么回事,稀里糊涂的先把题目过了。

这个去重为什么很难理解呢,所谓去重,其实就是使用过的元素不能重复选取。 这么一说好像很简单!

都知道组合问题可以抽象为树形结构,那么“使用过”在这个树形结构上是有两个维度的,一个维度是同一树枝上使用过,一个维度是同一树层上使用过。没有理解这两个层面上的“使用过” 是造成大家没有彻底理解去重的根本原因。

那么问题来了,我们是要同一树层上使用过,还是统一树枝上使用过呢?

回看一下题目,元素在同一个组合内是可以重复的,怎么重复都没事,但两个组合不能相同。

所以我们要去重的是同一树层上的“使用过”,同一树枝上的都是一个组合里的元素,不用去重

为了理解去重我们来举一个例子,candidates = [1, 1, 2], target = 3,(方便起见candidates已经排序了)

强调一下,树层去重的话,需要对数组排序!

选择过程树形结构如图所示:

可以看到图中,每个节点相对于 39.组合总和我多加了used数组,这个used数组下面会重点介绍。

回溯三部曲

  • 递归函数参数

与39.组合总和套路相同,此题还需要加一个bool型数组used,用来记录同一树枝上的元素是否使用过。

这个集合去重的重任就是used来完成的。

代码如下:

vector<vector<int>> result; // 存放组合集合
vector<int> path;           // 符合条件的组合
void backtracking(vector<int>& candidates, int target, int sum, int startIndex, vector<bool>& used) {
  • 递归终止条件

与39.组合总和相同,终止条件为 sum > targetsum == target

代码如下:

if (sum > target) { // 这个条件其实可以省略 return;
}
if (sum == target) {result.push_back(path);return;
}

sum > target 这个条件其实可以省略,因为和在递归单层遍历的时候,会有剪枝的操作,下面会介绍到。

  • 单层搜索的逻辑

这里与39.组合总和最大的不同就是要去重了。

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

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

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

这块比较抽象,如图:

我在图中将used的变化用橘黄色标注上,可以看出在candidates[i] == candidates[i - 1]相同的情况下:

  • used[i - 1] == true,说明同一树支candidates[i - 1]使用过
  • used[i - 1] == false,说明同一树层candidates[i - 1]使用过

这块去重的逻辑很抽象,网上搜的题解基本没有能讲清楚的,如果大家之前思考过这个问题或者刷过这道题目,看到这里一定会感觉通透了很多!

那么单层搜索的逻辑代码如下:

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;}sum += candidates[i];path.push_back(candidates[i]);used[i] = true;backtracking(candidates, target, sum, i + 1, used); // 和39.组合总和的区别1:这里是i+1,每个数字在每个组合中只能使用一次used[i] = false;sum -= candidates[i];path.pop_back();
}

注意sum + candidates[i] <= target为剪枝操作,在39.组合总和有讲解过!

C++代码

回溯三部曲分析完了,整体C++代码如下:

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

总结

本题同样是求组合总和,但就是因为其数组candidates有重复元素,而要求不能有重复的组合,所以相对于39.组合总和难度提升了不少。

关键是去重的逻辑,代码很简单,网上一搜一大把,但几乎没有能把这块代码含义讲明白的,基本都是给出代码,然后说这就是去重了,究竟怎么个去重法也是模棱两可

所以Carl有必要把去重的这块彻彻底底的给大家讲清楚,就连“树层去重”和“树枝去重”都是我自创的词汇,希望对大家理解有帮助!

就酱,如果感觉「代码随想录」诚意满满,就帮Carl宣传一波吧,感谢啦!

我是程序员Carl,可以找我组队刷题,也可以在B站上找到我,本文leetcode刷题攻略已收录,更多精彩算法文章尽在公众号:代码随想录,关注后就会发现和「代码随想录」相见恨晚!

如果感觉对你有帮助,不要吝啬给一个

「leetcode」40.组合总和II【回溯算法】详解!相关推荐

  1. leetcode系列--40.组合总和 II

    leetcode系列–第40题.组合总和 II 给你一个由候选元素组成的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合 ...

  2. LeetCode 40. 组合总和 II(回溯)

    题目描述 给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合. candidates 中的每个数字在每个组合中只能 ...

  3. 【LeetCode】40. 组合总和 II (JavaScript)

    原题 给定一个候选人编号的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合. candidates 中的每个数字在每个组 ...

  4. leetcode 39. 组合总和 40. 组合总和 II

    leetcode 39. 组合总和 40. 组合总和 II 组合总和 给定一个无重复元素的正整数数组 candidates 和一个正整数 target ,找出 candidates 中所有可以使数字和 ...

  5. Suzy找到实习了吗 Day27 | 回溯进行中:39. 组合总和,40. 组合总和 II,131.分割回文串

    39. 组合总和 题目 给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 , ...

  6. 77.组合 | 40.组合总和II | 39.组合总和 | 784.字母大小写全排列

    77.组合 给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合. 你可以按 任何顺序 返回答案. 示例 1: 输入:n = 4, k = 2 输出: [   [2,4], ...

  7. 回溯算法详解之全排列、N皇后问题

    回溯算法详解 回溯算法框架.解决一个回溯问题,实际上就是一个决策树的遍历过程.你只需要思考 3 个问题: 1.路径:也就是已经做出的选择. 2.选择列表:也就是你当前可以做的选择. 3.结束条件:也就 ...

  8. LeetCode 40. 组合总和 II(排列组合 回溯)

    1. 题目 给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合. candidates 中的每个数字在每个组合中只 ...

  9. python两两组合求和_LeetCode-python 40.组合总和 II

    题目链接 难度:中等       类型: 深度优先搜索 给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合. ca ...

  10. 回溯算法详解:理论+基础类回溯题解

    文章目录 五:括号生成问题 六:组合总和(点击跳转) 七:子集问题(点击跳转) 五:括号生成问题 这个问题也比较经典,题目的要求是让你生成所有可能的并且 有效的 括号组合. 对于括号问题,两个特性,请 ...

最新文章

  1. 8个开发必备的PHP功能
  2. vim为何打开文件乱码,而且之后setenc还是乱码?附带实用vimrc一份
  3. Java进阶之光!javaunicode码转字符
  4. 秒懂数据类型的真谛—Python基础前传(4)
  5. mysql 批量替换 所有表_[收藏]批量替换一个数据库中所有表中所有记录
  6. 【opencv学习】透视变换矩阵
  7. django 1.8 官方文档翻译: 2-5-10 数据库函数
  8. python通用权限管理框架图_PyCasbin: 支持 ACL、RBAC、ABAC 多种模型的 Python 权限管理框架...
  9. Python绘制Excel图表
  10. 软件测试正交表用在哪里,使用正交试验法设计测试用例中的一些常用的正交表...
  11. 将markdown文档转化为pdf格式
  12. s5p4418摄像头程序使用教程
  13. css实现超过两行用...表示
  14. 实例对比 Julia, R, Python,谁是狼语言?
  15. iBooks 书籍存放位置
  16. 3DMAX一键生成螺母和螺栓插件使用教程
  17. CF1132B Discounts题解
  18. FFmpeg:截取视频片段转成GIF动画
  19. 2020年8月编程语言排行榜出炉:C语言位居第一,Java、Python位居二三
  20. 激光雷达+imu_无人驾驶技术入门(四):无人车传感器 IMU 深入剖析

热门文章

  1. java注释指导手册
  2. poj 2955 Brackets 区间DP
  3. DEDECMS添加友情链接长度限制的详细解决方法
  4. 我的each方法——JavaScript Array
  5. Kafka Consumer API示例
  6. Linux 切换用户
  7. 二叉树前中后/层次遍历的递归与非递归形式(c++)
  8. MySQL 8 新特性之持久化全局变量的修改
  9. 删除安装的python
  10. Battery Charging Specification 1.2 中文详解 来源:www.chengxuyuans.com