【leetcode热题100】子集 II

news2024/11/16 4:30:26

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

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

示例 1:

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

示例 2:

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

解法一 回溯法

这个比较好改,我们只需要判断当前数字和上一个数字是否相同,相同的话跳过即可。当然,要把数字首先进行排序。

public List<List<Integer>> subsetsWithDup(int[] nums) {
    List<List<Integer>> ans = new ArrayList<>();
    Arrays.sort(nums); //排序
    getAns(nums, 0, new ArrayList<>(), ans);
    return ans;
}

private void getAns(int[] nums, int start, ArrayList<Integer> temp, List<List<Integer>> ans) {
    ans.add(new ArrayList<>(temp));
    for (int i = start; i < nums.length; i++) {
        //和上个数字相等就跳过
        if (i > start && nums[i] == nums[i - 1]) {
            continue;
        }
        temp.add(nums[i]);
        getAns(nums, i + 1, temp, ans);
        temp.remove(temp.size() - 1);
    }
}

时间复杂度:

空间复杂度:

解法二 迭代法

根据78题解法二修改。我们看一下如果直接按照 78 题的思路会出什么问题。之前的思路是,先考虑 0 个数字的所有子串,再考虑 1 个的所有子串,再考虑 2 个的所有子串。而求 n 个的所有子串,就是 【n - 1 的所有子串】和 【n - 1 的所有子串加上 n】。例如,

数组 [ 1 2 3 ] 
[ ]的所有子串 [ ]
[ 1 ] 个的所有子串 [ ] [ 1 ] 
[ 1 2 ] 个的所有子串 [ ] [ 1 ] [ 2 ][ 1 2 ]
[ 1 2 3 ] 个的所有子串 [ ] [ 1 ] [ 2 ] [ 1 2 ] [ 3 ] [ 1 3 ] [ 2 3 ] [ 1 2 3 ]
Copy

但是如果有重复的数字,会出现什么问题呢

数组 [ 1 2 2 ] 
[ ] 的所有子串 [ ]
[ 1 ] 的所有子串 [ ] [ 1 ] 
[ 1 2 ] 的所有子串 [ ] [ 1 ] [ 2 ][ 1 2 ]
[ 1 2 2 ] 的所有子串 [ ] [ 1 ] [ 2 ] [ 1 2 ] [ 2 ] [ 1 2 ] [ 2 2 ] [ 1 2 2 ]
Copy

我们发现出现了重复的数组,那么我们可不可以像解法一那样,遇到重复的就跳过这个数字呢?答案是否定的,如果最后一步 [ 1 2 2 ] 增加了 2 ,跳过后,最终答案会缺少 [ 2 2 ]、[ 1 2 2 ] 这两个解。我们仔细观察这两个解是怎么产生的。

我们看到第 4 行黑色的部分,重复了,是怎么造成的呢?

第 4 行新添加的 2 要加到第 3 行的所有解中,而第 3 行的一部分解是旧解,一部分是新解。可以看到,我们黑色部分是由第 3 行的旧解产生的,橙色部分是由新解产生的。

而第 1 行到第 2 行,已经在旧解中加入了 2 产生了第 2 行的橙色部分,所以这里如果再在旧解中加 2 产生黑色部分就造成了重复。

所以当有重复数字的时候,我们只考虑上一步的新解,算法中用一个指针保存每一步的新解开始的位置即可。

public List<List<Integer>> subsetsWithDup(int[] nums) {
    List<List<Integer>> ans = new ArrayList<>();
    ans.add(new ArrayList<>());// 初始化空数组
    Arrays.sort(nums);
    int start = 1; //保存新解的开始位置
    for (int i = 0; i < nums.length; i++) {
        List<List<Integer>> ans_tmp = new ArrayList<>();
        // 遍历之前的所有结果
        for (int j = 0; j < ans.size(); j++) {
            List<Integer> list = ans.get(j);
            //如果出现重复数字,就跳过所有旧解
            if (i > 0 && nums[i] == nums[i - 1] && j < start) {
                continue;
            }
            List<Integer> tmp = new ArrayList<>(list);
            tmp.add(nums[i]); // 加入新增数字
            ans_tmp.add(tmp);
        }

        start = ans.size(); //更新新解的开始位置
        ans.addAll(ans_tmp);
    }
    return ans;
}

时间复杂度:

空间复杂度:O(1)。

还有一种思路,参考这里,当有重复数字出现的时候我们不再按照之前的思路走,而是单独考虑这种情况。

当有 n 个重复数字出现,其实就是在出现重复数字之前的所有解中,分别加 1 个重复数字, 2 个重复数字,3 个重复数字 ... 什么意思呢,看一个例子。

数组 [ 1 2 2 2 ] 
[ ]的所有子串 [ ]
[ 1 ] 个的所有子串 [ ] [ 1 ] 
然后出现了重复数字 2,那么我们记录重复的次数。然后遍历之前每个解即可
对于 [ ] 这个解,
加 1 个 2,变成 [ 2 ] 
加 2 个 2,变成 [ 2 2 ]
加 3 个 2,变成 [ 2 2 2 ]
对于 [ 1 ] 这个解
加 1 个 2,变成 [ 1 2 ] 
加 2 个 2,变成 [ 1 2 2 ]
加 3 个 2,变成 [ 1 2 2 2 ]

代码的话,就很好写了。

public List<List<Integer>> subsetsWithDup(int[] num) {
    List<List<Integer>> result = new ArrayList<List<Integer>>();
    List<Integer> empty = new ArrayList<Integer>();
    result.add(empty);
    Arrays.sort(num);

    for (int i = 0; i < num.length; i++) {
        int dupCount = 0;
        //判断当前是否是重复数字,并且记录重复的次数
        while( ((i+1) < num.length) && num[i+1] == num[i]) {
            dupCount++;
            i++;
        }
        int prevNum = result.size();
        //遍历之前几个结果的每个解
        for (int j = 0; j < prevNum; j++) {
            List<Integer> element = new ArrayList<Integer>(result.get(j));
            //每次在上次的结果中多加 1 个重复数字
            for (int t = 0; t <= dupCount; t++) {
                element.add(num[i]); //加入当前重复的数字
                result.add(new ArrayList<Integer>(element));
            }
        }
    }
    return result;
}

解法三 位操作

本以为这个思路想不出来怎么去改了,然后看到了这里。

回顾一下,这个题的思想就是每一个数字,考虑它的二进制表示。

例如,nums = [ 1, 2 , 3 ]。用 1 代表在,0 代表不在。

1 2 3
0 0 0 -> [     ]
0 0 1 -> [    3]
0 1 0 -> [  2  ]   
0 1 1 -> [  2 3]  
1 0 0 -> [1    ]
1 0 1 -> [1   3] 
1 1 0 -> [1 2  ]
1 1 1 -> [1 2 3]

但是如果有了重复数字,很明显就行不通了。例如对于 nums = [ 1 2 2 2 3 ]。

1 2 2 2 3
0 1 1 0 0  -> [  2 2  ]
0 1 0 1 0  -> [  2 2  ]
0 0 1 1 0  -> [  2 2  ]

上边三个数产生的数组重复的了。三个中我们只取其中 1 个,取哪个呢?取从重复数字的开头连续的数字。什么意思呢?就是下边的情况是我们所保留的。

2 2 2 2 2 
1 0 0 0 0 -> [  2         ]
1 1 0 0 0 -> [  2 2       ]
1 1 1 0 0 -> [  2 2 2     ]
1 1 1 1 0 -> [  2 2 2 2   ]
1 1 1 1 1 -> [  2 2 2 2 2 ]

而对于 [ 2 2 ] 来说,除了 1 1 0 0 0 可以产生,下边的几种情况,都是产生的 [ 2 2 ]

2 2 2 2 2 
1 1 0 0 0 -> [  2 2       ]
1 0 1 0 0 -> [  2 2       ]
0 1 1 0 0 -> [  2 2       ]
0 1 0 1 0 -> [  2 2       ]
0 0 0 1 1 -> [  2 2       ]
......

怎么把 1 1 0 0 0 和上边的那么多种情况区分开来呢?我们来看一下出现了重复数字,并且当前是 1 的前一个的二进位。

对于 1 1 0 0 0 ,是 1。

对于 1 0 1 0 0 , 是 0。

对于 0 1 1 0 0 ,是 0。

对于 0 1 0 1 0 ,是 0。

对于 0 0 0 1 1 ,是 0。

......

可以看到只有第一种情况对应的是 1 ,其他情况都是 0。其实除去从开头是连续的 1 的话,就是两种情况。

第一种就是,占据了开头,类似于这种 10...1....

第二种就是,没有占据开头,类似于这种 0...1...

这两种情况,除了第一位,其他位的 1 的前边一定是 0。所以的话,我们的条件是看出现了重复数字,并且当前位是 1 的前一个的二进位。

所以可以改代码了。

public List<List<Integer>> subsetsWithDup(int[] num) {
    Arrays.sort(num);
    List<List<Integer>> lists = new ArrayList<>();
    int subsetNum = 1<<num.length;
    for(int i=0;i<subsetNum;i++){
        List<Integer> list = new ArrayList<>();
        boolean illegal=false;
        for(int j=0;j<num.length;j++){
            //当前位是 1
            if((i>>j&1)==1){
                //当前是重复数字,并且前一位是 0,跳过这种情况
                if(j>0&&num[j]==num[j-1]&&(i>>(j-1)&1)==0){
                    illegal=true;
                    break;
                }else{
                    list.add(num[j]);
                }
            }
        }
        if(!illegal){
            lists.add(list); 
        }

    }
    return lists;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1444128.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于javaEE的ssm仓库管理系统

仓库管理系统的重中之重是进销存分析这一板块&#xff0c;在这一板块中&#xff0c;顾名思义能够查询到近期的进货记录&#xff0c;包括每日的进货单据&#xff0c;单品推移(即某一商品的库存变化)&#xff0c;方便我们核对库存差异。同时也需要查询到每日的销售数据&#xff0…

Linux服务器安装Jenkins

1、安装Jenkins前必须先安装jdk与maven 2、下载Jenkins 安装包地址 linux jenkins 链接: 百度网盘 请输入提取码 提取码: zfyq 3、解压压缩包 rpm -ivh jenkins-2.174-1.1.noarch.rpm 4、解压完成后查看Jenkins安装路径 whereis jenkins 5、启动报错 &#xff0c;这是因为Jenki…

vs2013与对应的opencv3.2下载地址和环境配置

Visual Studio community 2013 • 链接&#xff1a; https://pan.baidu.com/s/1ye-EDDyUGsy8EaZKaq9Ksw 提取码&#xff1a; 06lw -- 来自百度网盘超级会员 V7 的分享 • 也可以从下面官网下载 https://learn.microsoft.com/zh-tw/visualstudio/releasenotes/vs2013-communit…

前端JavaScript篇之对象创建的方式有哪些?

目录 对象创建的方式有哪些&#xff1f;1. 工厂模式&#xff1a;2. 构造函数模式&#xff1a;3. 原型模式&#xff1a;4. 混合模式&#xff1a;5. 动态原型模式&#xff1a;6. 寄生构造函数模式&#xff1a;7. 字面量方式&#xff1a; 对象创建的方式有哪些&#xff1f; JavaS…

RUST入门:如何用vscode调试rust程序

RUST已经流行一阵子了&#xff0c;但是比较系统的IDE介绍还是比较少&#xff0c;这里我简单介绍 一下如何用vscode实现单步调试rust程序&#xff0c;就像我们平时调试c程序一样。 学习资料网站 首先&#xff0c;介绍几个学习rust的好网站&#xff0c; Rust程序设计语言Rust语…

vue.js基于springboot的实验室设备管理系统10345

(1)设备信息模块&#xff1a;记录设备的基本信息&#xff0c;如设备采购来源信息、设备需求量、当前数量、日期等。 (2) 用户模块&#xff1a;教师职工。实现对用户个人信息、消息管理和实验室设备的查询使用申请等。 (3) 管理员模块&#xff1a;实现对所有设备信息的增删改查&…

论文笔记:相似感知的多模态假新闻检测

整理了RecSys2020 Progressive Layered Extraction : A Novel Multi-Task Learning Model for Personalized Recommendations&#xff09;论文的阅读笔记 背景模型实验 论文地址&#xff1a;SAFE 背景 在此之前&#xff0c;对利用新闻文章中文本信息和视觉信息之间的关系(相似…

迅为RK3588开发板ubuntu和window互传图形界面直接拖拽进行文件传输

确保以及安装了 VMware Tools。如下图所示表示已安装过了。 和 windows 端文件夹间传输一样直接拖拽进去即可&#xff0c;如下图所示&#xff1a; 也可拖拽到终端&#xff0c;如下图所示&#xff1a; 更多内容可以B站搜索迅为RK3588开发板

不安全的 HTTP请求 漏洞原理以及修复方法

漏洞名称&#xff1a;不安全的HTTP方法、危险的HTTP方法 漏洞描述&#xff1a;不安全的HTTP方法一般包括&#xff1a;TRACE、PUT、DELETE、COPY 等。其中最常见的为TRACE方法可以回显服务器收到的请求&#xff0c;主要用于测试或诊断&#xff0c;恶意攻击者可以利用该方法进行…

【Linux】学习-基础IO—下

Linux基础IO—上 重定向 通过上篇的学习&#xff0c;我们了解了文件描述符的分配规则是遍历指针数组&#xff0c;用没有被使用的最小下标作为新的文件描述符&#xff0c;也就是我们可以通过关闭三个标准流文件并使用他们原先所占用的0&#xff0c;1&#xff0c;2描述符。 那…

2024 年 6 款最佳 PDF 编辑器,您可以免费获得

PDF 作为与 Windows、iOS、Linux 和各种其他操作系统兼容的安全文档格式而享有盛誉。这种广泛的兼容性使 PDF 成为一种流行的选择&#xff0c;几乎每个用户都会在不同的环境中遇到 PDF 文件。无论是合同、发票、电子书、信用卡对账单、银行对账单、税务表格还是保险文件&#x…

【芯片设计- RTL 数字逻辑设计入门 番外篇 9 -- SOC 中PL端与PS端详细介绍】

文章目录 Programmable Logic and Processing SystemPL&#xff08;Programmable Logic&#xff09;特点PS和PL之间的协同设计和开发工具 Programmable Logic and Processing System 在系统级芯片&#xff08;SoC&#xff09;的上下文中&#xff0c;“PL” 通常指的是可编程逻…

第二节 zookeeper基础应用与实战

目录 1. Zookeeper命令操作 1.1 Zookeeper 数据模型 1.2 Zookeeper服务端常用命令 1.3 Zookeeper客户端常用命令 1.3.1 基本CRUD 1.3.2 创建临时&顺序节点 2. Zookeeper JavaAPI操作 2.1 Curator介绍 2.2 引入Curator 2.3 建立连接 2.4 添加节点 2.5 修改节点 …

Blazor SSR/WASM IDS/OIDC 单点登录授权实例3-服务端管理组件

目录: OpenID 与 OAuth2 基础知识Blazor wasm Google 登录Blazor wasm Gitee 码云登录Blazor SSR/WASM IDS/OIDC 单点登录授权实例1-建立和配置IDS身份验证服务Blazor SSR/WASM IDS/OIDC 单点登录授权实例2-登录信息组件wasmBlazor SSR/WASM IDS/OIDC 单点登录授权实例3-服务端…

导数的定义【高数笔记】

【含义】可以抽象成&#xff0c;在一个极其短的时间段内&#xff0c;温度差 / 时间差 【本质】瞬间的平均值 【分类】可以分成几类&#xff1f;每类需要注意的点 【导数存在的必要条件】 【导数与极限的关系】可以参考导数的定义的式子 【题型解法】分几个题型&#xff1f;每个…

【MySQL进阶之路】生产案例:每一个月左右MySQL就会出现性能抖动问题

欢迎关注公众号&#xff08;通过文章导读关注&#xff1a;【11来了】&#xff09;&#xff0c;及时收到 AI 前沿项目工具及新技术的推送&#xff01; 在我后台回复 「资料」 可领取编程高频电子书&#xff01; 在我后台回复「面试」可领取硬核面试笔记&#xff01; 文章导读地址…

Linux第45步_通过搭建“DNS服务器”学习图形化配置工具

学习的意义&#xff1a;通过搭建“DNS服务器”&#xff0c;来学习“图形化配置工具”。“DNS服务器”&#xff0c;我们用不到&#xff0c;但为后期移植linux系统服务&#xff0c;因为在移植系统时&#xff0c;需要用到这个“图形化配置工具”。 1、“menuconfig图形化配置工具…

贾玲的腹肌,你也可以拥有

​​​​​​​ 贾玲的腹肌&#xff0c;你也可以拥有 大年初一&#xff0c;有学员来给顾问老师拜年&#xff0c;聊起了现在春节档热门电影&#xff0c;贾玲导演的第二部作品《热辣滚烫》&#xff0c;也聊起了她瘦身100斤后的模样。 学员&#xff1a;贾玲瘦了 100 斤&#xff0…

c语言中的隐式类型转换

数据类型转化 我们在实际编程中&#xff0c;不管你是有意的还是无意的&#xff0c;有时候都会让两个不同类型的数据参与运算&#xff0c;编译器为了能够生成CPU可以正常 执行的指令&#xff0c;往往会对数据做类型转换&#xff0c;将两个不同类型的数据转换成同一种数据类型。…

C++重新入门-循环

目录 1.循环类型 while循环&#xff1a; for循环 基于范围的for循环(C11) do...while 循环 2.循环控制语句 3.无限循环 有的时候&#xff0c;可能需要多次执行同一块代码。一般情况下&#xff0c;语句是顺序执行的&#xff1a;函数中的第一个语句先执行&#xff0c;接着…