当前位置: 首页 > article >正文

leetCode 90.子集 II + 回溯算法 + 图解 + 笔记

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

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

示例 1:

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

示例 2:

输入:nums = [0]
输出:[[],[0]]

  • 本题其实就是子集(leetCode 78.子集 + 回溯算法 + 图解 + 笔记)和组合总和 II (leetCode 40.组合总和 II + 回溯算法 + 剪枝 + used数组 + 图解)这两道题目的一个完美结合

 >>问题思考(O_O)?

1).什么是“去重”

  • “去重”:就是使用过的元素不能重复选取了

2).何为“树枝去重”“树层去重”?(代码随想录Carl老师自创的名词)

把组合问题抽象为树形结构used(“使用过”)在这个树形结构上是有两个维度的,一个维度表示是同一树枝上使用过,一个维度表示是同一树层上使用过

来看题目要求:“集合(数组nums)有重复元素,但还不能有重复的组合 ”。

例如{1,2,2},仅仅是两个元素的数值相同,并没有重复使用同一个元素。那分别是不同的两个元素,那它就是合法的,所以说树枝上前面取了2,后面我还能不能取2呢?可以的,因为它仅仅是你这两个元素数值都是2而已,但其实取的是两个不同的元素。所以说树枝上取了重复数值的元素(是两个不同的元素)可不可以?可以的,没问题(o´ω`o)و

但我树层上能不能取重复数值的元素呢?因为树层上你前面取2,后面取2,你得到的子集,它注定就是重复的。所以说树层上相邻两个元素(数值相同)重复选取的话,它得到的子集就是重复的子集。

  • 故去重的是同一树层上的“使用过”是不同组合里的元素,而对于同一树枝上的都是一个组合里的元素,不用去重。 
  • 强调注意:在树层去重时,需要对数组排序

(3)收获结果

子集问题组合问题分割问题都可以抽象成一棵树,不同的是:

  • 组合问题和分割问题都是收集树形结构中的叶子节点的结果
  • 子集问题收集树形结构中的所有节点的结果

>>回溯三部曲:

1).确定回溯函数参数

  • path来收集符合条件的结果
  • result 保存 path,作为结果集
  • startIndex 来控制for循环的起始位置
  • used 是bool型数组,用来记录同一树枝上的元素是否使用过
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& nums,vector<bool>& used,int startIndex) 

2).递归的终止条件(本题可以不写)

if(startIndex>=nums.size()) return;

 3).单层搜索的逻辑

去重逻辑if( i>0 && nums[i] == nums[i - 1] && used[i - 1] == false),表示前一个树枝,使用了nums[i - 1],也就是说同一树层使用过nums[i - 1]。那么此时 for 循环 里通过 continue 操作跳过此种情况的递归

for(int i=startIndex;i<nums.size();i++) {
    if(i>0 && nums[i]==nums[i-1] && used[i-1]==false) {
        continue;
    }
    ......
}

思考(O_O)?为啥used[i-1] == false能表示同一树层 nums[i-1] 使用过这种情况呢?

  • 是因为在同一树层used[i-1] == false 能表示当前取的nums[i]是从nums[i-1]回溯而来的。used[i]==true,表示进入下一层递归,取下一个数,所以在树枝上

C++代码: 

class Solution {
public:
    vector<vector<int>> result;
    vector<int> path;
    
    void backtracking(vector<int>& nums,vector<bool>& used,int startIndex) {
        result.push_back(path);
        // if(startIndex>=nums.size()) return;
        for(int i=startIndex;i<nums.size();i++) {
            if(i>0 && nums[i]==nums[i-1] && used[i-1]==false) {
                continue;
            }
            used[i]=true;
            path.push_back(nums[i]);
            backtracking(nums,used,i+1);
            used[i]=false;
            path.pop_back();
        }
    }
    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
        vector<bool> used(nums.size(),false);
        sort(nums.begin(), nums.end()); // 去重需要排序
        backtracking(nums,used,0);
        return result;
    }
};
  • 时间复杂度: O(n * 2^n)
  • 空间复杂度: O(n)

参考文章和推荐视频:

代码随想录 (programmercarl.com)icon-default.png?t=N7T8https://www.programmercarl.com/0090.%E5%AD%90%E9%9B%86II.html#%E6%80%9D%E8%B7%AF回溯算法解决子集问题,如何去重?| LeetCode:90.子集II_哔哩哔哩_bilibiliicon-default.png?t=N7T8https://www.bilibili.com/video/BV1vm4y1F71J/?spm_id_from=333.788&vd_source=a934d7fc6f47698a29dac90a922ba5a3


http://www.kler.cn/a/158372.html

相关文章:

  • FFmpeg的C++封装:FFmpegWrapper
  • vue form表单的封装--使用的是elementUI
  • 如何设计自动化测试脚本
  • VAE模型及pytorch实现
  • centos7.5插件xtrabackup下载
  • 【数据结构】链表OJ题(顺序表)(C语言实现)
  • 硬件开发笔记(十四):RK3568底板电路LVDS模块、MIPI模块电路分析、LVDS硬件接口、MIPI硬件接口详解
  • C/C++ 实现枚举网上邻居信息
  • nginx对多个服务器的高可用,容易出现鉴权失败
  • 二百一十二、Flume——Flume实时采集Linux中的目录文件写入到HDFS中(亲测、附截图)
  • Socket.IO 实现原理(一篇文章让你彻底弄懂即时聊天技术)
  • 算法通关村第一关—白银挑战—链表高频面试算法题—查找两个链表的第一个公共子节点
  • docker: Error response from daemon: failed to create shim task: OCI runtime
  • 5.清除SVN用户账号两种方式
  • 什么是网络可视化?网络可视化工具有用吗
  • java学习part37定制排序和自然排序
  • OpenCV实现手势音量控制
  • 【Qt开发流程】之对象模型1:信号和槽
  • Class Introduction and Tools Familiarization
  • 爆款开放式耳机哪一款性价比最高?3款热门机型推荐,小白速看