子集

力扣题目链接

给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。

解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。

示例 1:

输入:nums = [1,2,3]

输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]

示例 2:

输入:nums = [0]

输出:[[],[0]]

思路

如果把 子集问题、组合问题、分割问题都抽象为一棵树的话,那么组合问题和分割问题都是收集树的叶子节点,而子集问题是找树的所有节点

其实这题就是模板题,与 组合总和分割回文串 不同的是,这题需要全部收集遍历到的值,包括最开始path为空也要算进结果数组中

那就套用回溯问题的解题模板即可,真的就是直接套

代码

class Solution {
private:
//定义结果数组
vector<int> path;
vector<vector<int>> res;
//确定回溯函数的参数和返回值
void backtracking(vector<int>& nums, int beginIndex){
res.push_back(path);//放在这里,不会把path为空的情况漏掉
//确定停止条件
if(beginIndex >= nums.size()){//写不写都行,因为当满足条件时for循环也到头了,也会自己停止
return;
}
//确定单层处理逻辑
//直接遍历获取树结构中的所有值
for(int i = beginIndex; i < nums.size(); ++i){
path.push_back(nums[i]);
backtracking(nums, i + 1);//因为子集不能有重复的,因此要跳过本次的值
path.pop_back();
}
} public:
vector<vector<int>> subsets(vector<int>& nums) {
backtracking(nums, 0);
return res;
}
};

关于backtracking(nums, i + 1)为什么要跳,对比看->组合总和思路部分单层处理逻辑来理解

子集II

力扣题目链接(opens new window)

给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。

说明:解集不能包含重复的子集。

示例:

  • 输入: [1,2,2]
  • 输出: [ [2], [1], [1,2,2], [2,2], [1,2], [] ]

思路

相较 子集I ,这题题目加了“可能包含重复元素,但不能包含重复子集

这意味着什么?又得去重

经过 组合总合II 的训练,现在你知道了我们可以使用一个布尔数组used来标记用过的元素,并通过该数组的状态以及相邻相同元素来进行去重操作

本题的其他部分代码与 子集I 区别不大,在其基础之上添加去重逻辑就行了

正好这里可以很明显的体现出去重逻辑应该写在哪些地方

代码

class Solution {
private:
//定义结果数组
vector<int> path;
vector<vector<int>> res;
//确定回溯函数的参数和返回值
void backtracking(vector<int>& nums, int beginIndex, vector<bool>& used){
res.push_back(path);//放在这里,不会把path为空的情况漏掉
//确定停止条件
if(beginIndex >= nums.size()){//写不写都行,因为当满足条件时for循环也到头了,也会自己停止
return;
}
//确定单层处理逻辑
//直接遍历获取树结构中的所有值
for(int i = beginIndex; i < nums.size(); ++i){
if(i > 0 && nums[i] == nums[i - 1] && used[i - 1] == 0){//去重逻辑详见 组合总和II
continue;//在单层遍历时有重复值就跳过
}
path.push_back(nums[i]);
used[i] = true;//记录使用的元素
backtracking(nums, i + 1, used);//因为子集不能有重复的,因此要跳过本次的值
used[i] = false;//回溯
path.pop_back();
}
} public:
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
vector<bool> used(nums.size(), false);
//排序
sort(nums.begin(), nums.end());
backtracking(nums, 0, used);
return res;
}
};

与上一题的代码对比来看不难发现

新增的去重逻辑大部分都在单层处理部分

这也和之前我们在组合总和II中分析的一致,即:在被视为树结构的回溯问题中,“树层”中出现重复是不行的,但是纵向的“树枝”中可以出现重复

下面总结一下目前使用的回溯算法模板中的去重问题

去重操作总结

本题与组合总和II都应用了“去重”这一操作

这两个问题都在题干中给出了类似的要求

(本题)“给定一个可能包含重复元素的整数数组 nums...解集不能包含重复的组合”

(组合)“candidates 中的每个数字在每个组合中只能使用一次...解集不能包含重复的组合”

发现什么了吗?

这类题目都会给一个有重复值(或者暗示有重复值)的数据,然后让你找出所有组合,并且组合不能有重复

以后看见这些字眼要想到去重操作

再回顾一下出现重复值的本质

重复值的本质

当我们对一个已经排序了的数据进行回溯遍历时,当相邻的两个重复值中的前一个已经通过递归遍历完树结构的一个分支后,此时因为回溯我们会回到最开始触发递归的位置,然后继续从相邻重复值的后一个值再次开启递归遍历

那之后开启的递归遍历中的所有组合(或者是需要获取的某种结果)一定都在前一次递归遍历中出现过了

这就是重复值的本质

处理方法
逻辑

为了处理上述问题,我们引入一个布尔数组used来记录当前使用过的元素

used数组的大小会和题目给的数组大小一样(为了一一对应数组中的元素)

当我们使用递归到单层处理部分时,此时我们会记录组合结果,同时我们也在used中记录当前组合使用到的元素

等到下一次再记录组合结果之前,就判断一下used的状态

关键点来了,也就是重复的条件

重复的条件

重复的条件是,判断当前遍历到的元素(也就是要用到组合中的元素)的前一个元素与其本身是否构成相邻重复值

也就是当前这个元素在上次遍历时是否也出现过

如果确实出现过,那么再看当前used的前一个元素位置是否有记录

如果有记录(used[i - 1] != 0),那么意味着本次递归是处于树结构的一条枝干中,可以使用重复值

如果没有记录(used[i - 1] == 0),说明本次递归的上一次递归已经使用过相邻重复值了,那么本次递归再进行下去的结果将全部被包含于上次的结果中,因此要跳过本次for循环,使用下一个值进行递归遍历寻找新的组合

可见,used数组是负责判断当前处于"树枝"还是"树层"的一个辅助工具

而关键点还是得有相邻重复值,这是造成重复值出现的本质核心

步骤总结

1、在回溯函数中引入used布尔数组

2、在单层处理逻辑中加入对于相邻重复值和used状态的判断

3、在主函数中对用来寻找组合的数组进行提前排序(使用sort)

以后有新理解再补充

【LeetCode回溯算法#07】子集问题I+II,巩固解题模板并详解回溯算法中的去重问题的更多相关文章

  1. BM算法  Boyer-Moore高质量实现代码详解与算法详解

    Boyer-Moore高质量实现代码详解与算法详解 鉴于我见到对算法本身分析非常透彻的文章以及实现的非常精巧的文章,所以就转载了,本文的贡献在于将两者结合起来,方便大家了解代码实现! 算法详解转自:h ...

  2. SVD在推荐系统中的应用详解以及算法推导

    SVD在推荐系统中的应用详解以及算法推导     出处http://blog.csdn.net/zhongkejingwang/article/details/43083603 前面文章SVD原理及推 ...

  3. Java虚拟机详解04----GC算法和种类【重要】

    [声明] 欢迎转载,但请保留文章原始出处→_→ 生命壹号:http://www.cnblogs.com/smyhvae/ 文章来源:http://www.cnblogs.com/smyhvae/p/4 ...

  4. 详解zkw算法解决最小费用流问题

    网络流的一些基本概念 很多同学建立过网络流模型做题目, 也学过了各种算法, 但是对于基本的概念反而说不清楚. 虽然不同的模型在具体叫法上可能不相同, 但是不同叫法对应的思想是一致的. 下面的讨论力求规 ...

  5. 深入理解SVM,详解SMO算法

    今天是机器学习专题第35篇文章,我们继续SVM模型的原理,今天我们来讲解的是SMO算法. 公式回顾 在之前的文章当中我们对硬间隔以及软间隔问题都进行了分析和公式推导,我们发现软间隔和硬间隔的形式非常接 ...

  6. Java虚拟机详解04----GC算法和种类

    [声明] 欢迎转载,但请保留文章原始出处→_→ 生命壹号:http://www.cnblogs.com/smyhvae/ 文章来源:http://www.cnblogs.com/smyhvae/p/4 ...

  7. [LeetCode] Subsets I (78) & II (90) 解题思路,即全组合算法

    78. Subsets Given a set of distinct integers, nums, return all possible subsets. Note: Elements in a ...

  8. (原创)详解KMP算法

    KMP算法应该是每一本<数据结构>书都会讲的,算是知名度最高的算法之一了,但很可惜,我大二那年压根就没看懂过~~~ 之后也在很多地方也都经常看到讲解KMP算法的文章,看久了好像也知道是怎么 ...

  9. 详解KMP算法

    转载注明出处:http://www.cnblogs.com/yjiyjige/p/3263858.html 什么是KMP算法: KMP是三位大牛:D.E.Knuth.J.H.Morris和V.R.Pr ...

  10. 详解KMP算法【转】

    本文转载自:http://www.cnblogs.com/yjiyjige/p/3263858.html KMP算法应该是每一本<数据结构>书都会讲的,算是知名度最高的算法之一了,但很可惜 ...

随机推荐

  1. echart 悬浮窗超边界了怎么办?

    悬浮窗超边界了怎么办? 在渲染界面函数里面 写一个 // tooltip浮窗未知 chartOption.tooltip.position = function(point, params, dom, ...

  2. 记一次ajax文件上传

    一个新需求提交页面. 在页面提交的时候,使用的是,先上传文件再上传表单 在这里需要返回表单存储的文件地址,需要上传.所以.在上传文件之后会返回存储的地址. 这里犯的一个错误: 往input 的type ...

  3. java中的数组遍历(简便小方法)

    int[] a = {1,2,3,4}; for(int k:a){ System.out.println(k); //注意冒号后面直接写数组名即可. //注意,k的值就是数组a中某一项的值,即语句& ...

  4. HTML复习(18.图片样式)

    重点 掌握图片样式属性(大小.边框.对齐)了解float属性 图片大小在CSS中,我们也是使用width和height这2个属性来定义图片的大小(也就是宽度和高度).在实际开发中,如果你需要多大的图片 ...

  5. 手动导入jar

    mvn install:install-file -Dfile=D:\java_tools\maven\maven-repository\cn\afterturn\easypoi-base-cy\4. ...

  6. vue父子件,子件页面table数据列按条件显示不同的内容

    需求:在父件中点击按钮.子件弹框中,table列根据条件显示不同的数据 实现思路:点击按钮,执行不同的方法,方法中参数值不同,从而展示不同的columns. 父件按钮如下图: 父件中导入子件需要注意的 ...

  7. mangodb查询语句

    1.查询所有记录 db.userInfo.find(); 相当于: select* from userInfo; 默认每页显示20条记录,当显示不下的情况下,可以用it迭代命令查询下一页数据.注意:键 ...

  8. linux开机自启动tomcat或者其他应用

     开机自启动Tomcat: 1.创建一个脚本,touch tomcat_start.sh 2.编辑脚本,vim tomcat_start.sh #!/bin/sh #chkconfig: 2345 8 ...

  9. python基础篇 13-模块的导入 安装第三方模块

    一.模块 一个python文件就是一个模块 标准模块(内置模块) 第三方模块 需要自己安装的 自己写的 需要导入的 import 一个模块的实质: 实际上就是把一个py文件从头到尾执行了一遍,main ...

  10. 第9章 使用MVC为移动和客户端应用程序创建Web API(ASP.NET Core in Action, 2nd Edition)

    本章包括 创建Web API控制器以向客户端返回JSON 使用属性路由自定义URL 使用内容协商生成响应 使用[ApiController]属性应用通用约定 在前五章中,您已经完成了服务器端渲染ASP ...