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. 利用 device_map、torch.dtype、bitsandbytes 压缩模型参数控制使用设备

    为了更好的阅读体验,请点击这里 device_map 以下内容参考 Huggingface Accelerate文档:超大模型推理方法 在 HuggingFace 中有个重要的关键字是 device_ ...

  2. ubuntu 18.04 设置开机自启

    ubuntu 18.04 设置开机自启 背景 为了添加一些自定义的服务,例如autossh. 原文(有删改):https://zhuanlan.zhihu.com/p/98804785 介绍 ubun ...

  3. scarpy基础

    1. 创建项目 scrapy startproject 项目名称 2. 进入项目 cd 项目名称 3. 创建爬虫 scrapy genspider 名字 域名 4. 可能需要start_urls,修改 ...

  4. EC热键问题

    EC热键问题 ec 问题描述 ACPI事件监控 按键监控 UDEV事件监控 kprobe探测 初步总结热键功能流程 调试记录 PS2 问题描述 系统无触摸板打开和关闭的提示 已知热键功能 快捷键 功能 ...

  5. Spring Boot 整合

    什么是Spring Boot? 百度百科一下 创建Spring Boot项目 通过官网来创建(了解) 这里面的创建方式不做过多说明,只需要在 官网 里面创建好了,然后下载解压,就可以了,我这里直接使用 ...

  6. CosyVoice多语言、音色和情感控制模型,one-shot零样本语音克隆模型本地部署(Win/Mac),通义实验室开源

    近日,阿里通义实验室开源了CosyVoice语音模型,它支持自然语音生成,支持多语言.音色和情感控制,在多语言语音生成.零样本语音生成.跨语言声音合成和指令执行能力方面表现卓越. CosyVoice采 ...

  7. 树莓派4B-控制直流电机

    树莓派4B-控制直流电机 一.硬件介绍 树莓派 L298N电机驱动模块 直流电机 外接电源 杜邦线 二.硬件连接 原理图: 注意:ENA和ENB的跳冒必须安上去,如果没有拿杜邦线连通! 三.代码编写 ...

  8. JVM学习笔记-如何在IDEA打印JVM的GC日志信息

    若要在Idea上打印JVM相应GC日志,其实只需在Run/Debug Configurations上进行设置即可. 拿<深入Java虚拟机>书中的3-7代码例子来演示,如 1 public ...

  9. 洛谷P2845

    蓝题搜索,模拟上的细节稍微有点麻烦 #include<iostream> #include<utility> #include<vector> #include&l ...

  10. ES6拼接数组与小程序本地存储

    拼接数组 ES6扩展运算符[三个点(...)将一个数组转为用逗号分隔的参数序列] goodsList: [...goodsList, ...goods] 本地存储 // 把接口数据存入本地存储中 wx ...