LeetCode40.组合总和II

力扣题目链接(opens new window)

给定一个数组 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)]()如下区别:

  1. 本题candidates 中的每个数字在每个组合中只能使用一次。
  2. 本题数组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;
}
};

注意:

LeetCode40.组合总和II的更多相关文章

  1. [Swift]LeetCode40. 组合总和 II | Combination Sum II

    Given a collection of candidate numbers (candidates) and a target number (target), find all unique c ...

  2. LeetCode-40. 组合总和 II C++(回溯法)

    回溯法本身是种暴力解法,虽然效率之类的比较低,但是写起来比较易懂和快.在提交之后的排名也挺低的,大概就超过8%左右.以后复习的时候再去看看题解,看看更高性能的算法.这里先暂时贴上回溯法的代码. 最后说 ...

  3. Leetcode之回溯法专题-40. 组合总和 II(Combination Sum II)

    Leetcode之回溯法专题-40. 组合总和 II(Combination Sum II) 给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使 ...

  4. Java实现 LeetCode 40 组合总和 II(二)

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

  5. 40. 组合总和 II + 递归 + 回溯 + 记录路径

    40. 组合总和 II LeetCode_40 题目描述 题解分析 此题和 39. 组合总和 + 递归 + 回溯 + 存储路径很像,只不过题目修改了一下. 题解的关键是首先将候选数组进行排序,然后记录 ...

  6. 组合总和 II

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

  7. LeetCode 39. 组合总和 40.组合总和II 131.分割回文串

    欢迎关注个人公众号:爱喝可可牛奶 LeetCode 39. 组合总和 40.组合总和II 131.分割回文串 LeetCode 39. 组合总和 分析 回溯可看成对二叉树节点进行组合枚举,分为横向和纵 ...

  8. 四种语言刷算法之 组合总和 II

    力扣40. 组合总和 II 1.C void back(int* candidates, int candidatesSize, int target,int start,int *path,int ...

  9. LeetCode 中级 - 组合总和II(105)

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

  10. 40组合总和II

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

随机推荐

  1. parsel的使用

    介绍 parsel这个库可以解析HTML和XML,并支持使用XPath和CSS选择器对内容进行提取和修改,同时还融合了正则表达式的提取功能.parsel灵活强大,同时也是Python最流行的爬虫框架的 ...

  2. vue.config.js配置优化

    vue.config.js完整代码如下: 'use strict'; // Template version: 1.3.1 // see http://vuejs-templates.github.i ...

  3. python 二次封装logging,导致日志输出的filename错误及优化封装

    问题 封装logging文件名称为:A.py 调用A模块的文件名称为:B.py 二次封装了logging日志模块,根据需要,传入level,判断等级,调用logging模块的info.debug等日志 ...

  4. 一款开源、免费、现代化风格的WPF UI控件库 - ModernWpf

    前言 今天大姚给大家分享一款开源(MIT License).免费.现代化风格的WPF UI控件库:ModernWpf. 项目介绍 ModernWpf是一个开源项目,它为 WPF 提供了一组现代化的控件 ...

  5. RD/RT格式

    一.RD RD长度是8字节,包括Type和Value字段. Type字段:2字节 Value字段:6字节 Type字段的取值决定了Value字段的结构.每种类型的Value字段都由两个部分组成,分别是 ...

  6. mermaid语法画图

    mermaid 脚本语言 graph TB 从上到下 graph BT 从下到上 graph RL 从右到左 graph LR 从左到右 graph LR; A001-->B001; graph ...

  7. spark共享变量---广播变量和累加变量

    从三个方面来分析:1.什么时候使用,2.原理是什么3.性能上有什么优化 累加变量:--(自定义累加器很重要) 使用场景:累加器的一个常见用途是在调试时对作业执行过程中的事件进行计数,如:统计日志中空行 ...

  8. .NET CORE 部署提示 An error occurred while starting the application.

    错误提示: 解决方法 检查一下nuget引用包 是否更新了版本,如果升级或者降级了版本,需要将新的dll文件更新一下

  9. ChiFAN 的进程表

    ChiFAN 的进程表 tip 有些题写了题解,思路做法都在里面,就只丢一个传送门了. 2023.1.9 生日蛋糕 传送门 IDA* 经过一番推式子可得,若还剩下 \(K\) 的体积,表面积为 \(2 ...

  10. git push提交出现Everything up-to-date提示问题

    以前通过git提交代码到GitHub上的个人main分支时,曾出现过这样一个很低级的错误-- 出现这个错误原因,其实就是没有正确执行指令造成的,也就是没有正常提交数据. 一般按照以下命令提交,基本就没 ...