LeetCode40.组合总和II
LeetCode40.组合总和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]
]
思路
这道题目和[LeetCode39. 组合总和 - Tomorrowland_D - 博客园 (cnblogs.com)]()如下区别:
- 本题candidates 中的每个数字在每个组合中只能使用一次。
- 本题数组candidates的元素是有重复的,而[LeetCode39. 组合总和 - Tomorrowland_D - 博客园 (cnblogs.com)]()是无重复元素的数组candidates
最后本题和[LeetCode39. 组合总和 - Tomorrowland_D - 博客园 (cnblogs.com)]()要求一样,但是解集不能包含重复的组合。
本题的难点在于区别2中:集合(数组candidates)有重复元素,但还不能有重复的组合。
我们直观的可以想到以下办法:我把所有组合求出来,再用set或者map去重,这么做很容易超时!
所以要在搜索的过程中就去掉重复组合。
这个去重为什么很难理解呢,所谓去重,其实就是使用过的元素不能重复选取。 这么一说好像很简单!
都知道组合问题可以抽象为树形结构,那么“使用过”在这个树形结构上是有两个维度的,一个维度是同一树枝上使用过,一个维度是同一树层上使用过。没有理解这两个层面上的“使用过” 是造成大家没有彻底理解去重的根本原因。
那么问题来了,我们是要同一树层上使用过,还是同一树枝上使用过呢?
回看一下题目,元素在同一个组合内是可以重复的,怎么重复都没事,但两个组合不能相同。
所以我们要去重的是同一树层上的“使用过”,同一树枝上的都是一个组合里的元素,不用去重。
为了理解去重我们来举一个例子,candidates = [1, 1, 2], target = 3,(方便起见candidates已经排序了)
强调一下,树层去重的话,需要对数组排序!

1.递归的参数
- 和Leetcode39组合一样,需要result存放结果,path存放单条路径
- sum来存放当前的所有和
- startindex来标志当前遍历的位置
- 还需要一个used数组来用于去重,在下面会重点介绍去重!!!
2.递归的结束条件
- 与上题一样,当sum>=targetSum就返回,如果等于,我们就收集结果
3.单层搜索的逻辑
这里与LeetCode39.组合总和最大的不同就是要去重了。
前面我们提到:要去重的是“同一树层上的使用过”,如何判断同一树层上元素(相同的元素)是否使用过了呢。
如果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]使用过
为什么 used[i - 1] == false 就是同一树层呢,因为同一树层,used[i - 1] == false 才能表示,当前取的 candidates[i] 是从 candidates[i - 1] 回溯而来的。
而 used[i - 1] == true,说明是进入下一层递归,去下一个数,所以是树枝上,如图所示
单层递归的代码如下:
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();
}
代码:
class Solution {
public:
vector<int> path;
vector<vector<int> > result;
void backtracking(vector<int> candidates, int targetSum, int sum, int startindex, vector<bool>& used) {
if (sum >= targetSum) {
if (sum == targetSum) result.push_back(path);
return;
}
//这里的剪枝过程在组合总和中有讲到过!
for (int i = startindex; i < candidates.size() && sum + candidates[i]<=targetSum; 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] == 0) continue;
sum += candidates[i];
path.push_back(candidates[i]);
used[i] = 1;
//要注意这里是i+1,与之前讲解的组合总和不同,这里不能够选取重复的元素
backtracking(candidates, targetSum, sum, i + 1, used);
sum -= candidates[i];
path.pop_back();
//回溯的时候将之前使用过的元素置为0,标志着这是同一层的元素(树层),而不是树枝上的元素
used[i] = 0;
}
}
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
path.clear(); result.clear();
if (candidates.size() == 0) return result;
vector<bool> used(candidates.size(), 0);
//注意这里一定要排序
sort(candidates.begin(), candidates.end());
backtracking(candidates, target, 0, 0, used);
return result;
}
};
注意:
- 本文中还多次引用到了作者代码随想录 的原图,想要深入了解的可以关注原作者,阅读原作者的文章![代码随想录 (programmercarl.com)]()
LeetCode40.组合总和II的更多相关文章
- [Swift]LeetCode40. 组合总和 II | Combination Sum II
Given a collection of candidate numbers (candidates) and a target number (target), find all unique c ...
- LeetCode-40. 组合总和 II C++(回溯法)
回溯法本身是种暴力解法,虽然效率之类的比较低,但是写起来比较易懂和快.在提交之后的排名也挺低的,大概就超过8%左右.以后复习的时候再去看看题解,看看更高性能的算法.这里先暂时贴上回溯法的代码. 最后说 ...
- Leetcode之回溯法专题-40. 组合总和 II(Combination Sum II)
Leetcode之回溯法专题-40. 组合总和 II(Combination Sum II) 给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使 ...
- Java实现 LeetCode 40 组合总和 II(二)
40. 组合总和 II 给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合. candidates 中的每个数字在 ...
- 40. 组合总和 II + 递归 + 回溯 + 记录路径
40. 组合总和 II LeetCode_40 题目描述 题解分析 此题和 39. 组合总和 + 递归 + 回溯 + 存储路径很像,只不过题目修改了一下. 题解的关键是首先将候选数组进行排序,然后记录 ...
- 组合总和 II
组合总和 II 题目介绍 给定一个候选人编号的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合. candidates ...
- LeetCode 39. 组合总和 40.组合总和II 131.分割回文串
欢迎关注个人公众号:爱喝可可牛奶 LeetCode 39. 组合总和 40.组合总和II 131.分割回文串 LeetCode 39. 组合总和 分析 回溯可看成对二叉树节点进行组合枚举,分为横向和纵 ...
- 四种语言刷算法之 组合总和 II
力扣40. 组合总和 II 1.C void back(int* candidates, int candidatesSize, int target,int start,int *path,int ...
- LeetCode 中级 - 组合总和II(105)
给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合. candidates 中的每个数字在每个组合中只能使用一次. ...
- 40组合总和II
题目:给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合.candidates 中的每个数字在每个组合中只能使用一 ...
随机推荐
- Jenkins构建项目遇到的问题总结
4.2.1 在Windows下,Jenkins运行python项目 https://www.jianshu.com/p/f6edbaaa8a0d 4.2.2 配置不同类型的项目的操作步骤 http ...
- Wireshark抓包分析理解DHCP协议及工作流程
一.DHCP简介 DHCP(Dynamic Host Configuration Protocol)动态主机配置协议,前身是BOOTP协议.在大型局域网中,需要给很多主机配置地址信息,如果采用传统 ...
- ZYNQ:使用PetaLinux打包 BOOT.BIN、image.ub
说明 个人还是比较喜欢灵活去管理各个部分的源码. 有关文章: ZYNQ:PetaLinux提取Linux和UBoot配置.源码 编译Linux 取得Linux源代码和配置后,可以在其中执行make,编 ...
- C# Linq俩个list<Datarow> 取差集,并自定义字段
可以自定义类 ,也可以从参考官网文档:Enumerable.Except 方法 (System.Linq) | Microsoft Learn List<DataRow> list1 = ...
- 在VisualStudio中WPF应用程序在打开窗体界面设计时报错<发生了未经处理的异常>的解决方法
在网上找了一个wpf的开源项目,在打开窗体,点击设计的时候,提示错误信息如下 System.Resources.MissingSatelliteAssemblyExceptionThe satelli ...
- SpringBoot实现RequestBodyAdvice和ResponseBodyAdvice接口
Spring Boot 提供了一种机制,允许开发者在请求体(RequestBody)和响应体(ResponseBody)被处理之前和之后执行自定义逻辑.这通过 RequestBodyAdvice 和 ...
- SQL去重distinct方法解析
来源:https://www.cnblogs.com/lixuefang69/p/10420186.html SQL去重distinct方法解析 一 distinct 含义:distinct用来查询不 ...
- PHP中substr() mb_substr() mb_struct()的区别和用法
PHP substr() 函数可以分割文字,但要分割的文字如果包括中文字符往往会遇到问题,这时可以用mb_substr()/mb_strcut这个函 数,mb_substr() /mb_strcut的 ...
- NodeJS中Buffer与字符串相互转换时一个值得注意的问题
什么问题 如果一个Buffer的 toString() 结果为乱码或含有乱码,那么用此字符串以 Buffer.from()方法构造出来的Buffer将与原来的Buffer不相同. 这一点其实很好理解, ...
- oeasy教您玩转vim - 91 - # vim脚本编程展望
vim脚本编程展望 回忆 上次我们彻底研究了vim高亮的原理 各种语法项syntax item 关键字keyword 匹配模式match 区域region 定义好了之后还可以设置链接成组 hi d ...