子集与全排列问题(力扣78,90,46,47)

系列文章目录

子集和全排列问题与下面的组合都是属于回溯方法里的,相信结合前两期,再看这篇笔记,更有助于大家对本系列的理解

一、组合回溯问题
二、组合总和问题


文章目录

  • 系列文章目录
  • 题目
  • 子集
    • 一、思路
    • 二、解题方法
    • 三、Code
  • 子集II
    • 一、思路
    • 二、解题方法
    • 三、Code
  • 全排列
    • 一、思路
    • 二、解题方法
    • 三、Code
  • 全排列II
    • 一、思路
    • 二、解题方法
    • 三、Code
  • 总结


题目

Problem: 78. 子集
Problem: 90. 子集 II
Problem: 46. 全排列
Problem: 47. 全排列 II

子集II与全排列II都只是在前面的基础上加了题目所给的nums数组里的元素可重复这个条件
子集问题
给你一个整数数组 nums ,数组中的元素互不相同 。返回该数组所有可能的
子集(幂集)。解集 不能包含重复的子集。你可以按任意顺序返回解集。

示例 1:

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

示例 2:

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

全排列问题
给定一个不含重复数字的数组 nums ,返回其所有可能的全排列 。你可以按任意顺序返回答案。

示例 1:

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

示例 2:

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

示例 3:

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


子集

一、思路

与组合和组合总和问题不同的是每遍历到一个树的结点,都要把结果加入到result结果集里面,而不是到叶子结点时,才收集结果

例:从图中红线部分,可以看出遍历这个树的时候,把所有节点都记录下来,就是要求的子集集合
在这里插入图片描述

二、解题方法

回溯三部曲

  1. 递归函数的返回值以及参数:
    参数为startIndex和nums整数数组
 public void backTracking(int[] nums,int startIndex)
  1. 回溯函数终止条件:
    到叶子结点时就退出递归,去遍历其它路径上的结果
if(startIndex >= nums.length) {
            return;
        }
  1. 单层搜索的过程:
    每遍历一次都把元素添加到路径path中,递归回溯,每次递归startIndex都从后一位递归,否则会造成重复的组合。
for(int i = startIndex;i<nums.length;i++) {
            path.add(nums[i]);
            backTracking(nums,i+1);
            path.removeLast();
        }

在每层递归函数里,都要把当前路径添加到result结果集中,要写在终止条件之前,否则退出之后,当前结果还没有保存就回溯其它路径上的结果了。

result.add(new ArrayList<>(path));

优化:在这里其实可以省略掉终止条件,因为就算遍历超过叶子结点了,不满足for循环的条件里,for循环里面也不会执行,就直接return了。

三、Code

class Solution {
    List<List<Integer>> result = new ArrayList<>();
    LinkedList<Integer> path = new LinkedList<>();

    public List<List<Integer>> subsets(int[] nums) {
        backTracking(nums,0);
        return result;
    }

    public void backTracking(int[] nums,int startIndex) {
        result.add(new ArrayList<>(path));
        if(startIndex >= nums.length) {
            return;
        }
        for(int i = startIndex;i<nums.length;i++) {
            path.add(nums[i]);
            backTracking(nums,i+1);
            path.removeLast();
        }
    }
}

子集II

一、思路

这道题与子集I的区别在于题目给的数组元素可重复,这就要对组合结果去重,与组合总和II是相同的套路,都是先排序之后,再判断前面同一层是否出现了相同的访问过的元素,如果出现了则直接跳过这次循环

在这里插入图片描述

二、解题方法

与上面的子集问题I多了先对nums数组进行排序,在for循环内对横向遍历去重,去重的方法和组合总和II是一样的有以下两种:

  • used数组:
    如果前面的元素和当前元素相同,并且i>0,前面的元素没有使用过,说明不是同一条路径下使用过的的元素,而是同一层内的
if (i > 0 && nums[i] == nums[i - 1] && !used[i - 1]){
                continue;
            }
  • startIndex:
    如果前面的元素和当前元素相同,并且startIndex已经不是第一个开始遍历的,则直接跳过这次循环
if(i > startIndex && nums[i] == nums[i-1]){
                continue;
            }

三、Code

used数组

class Solution {
   List<List<Integer>> result = new ArrayList<>();// 存放符合条件结果的集合
   LinkedList<Integer> path = new LinkedList<>();// 用来存放符合条件结果
   boolean[] used;
    public List<List<Integer>> subsetsWithDup(int[] nums) {
        Arrays.sort(nums);
        used = new boolean[nums.length];
        subsetsWithDupHelper(nums, 0);
        return result;
    }
    
    private void subsetsWithDupHelper(int[] nums, int startIndex){
        result.add(new ArrayList<>(path));

        if (startIndex >= nums.length){
            return;
        }

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

            path.add(nums[i]);
            used[i] = true;
            subsetsWithDupHelper(nums, i + 1);
            path.removeLast();
            used[i] = false;
        }
    }
}

startIndex

class Solution {
    List<List<Integer>> result = new ArrayList<>();
    LinkedList<Integer> path = new LinkedList<>();

    public List<List<Integer>> subsetsWithDup(int[] nums) {
        Arrays.sort(nums);
        backTracking(nums,0);
        return result;
    }

    public void backTracking(int[] nums,int startIndex) {
        result.add(new ArrayList<>(path));

        if(startIndex >= nums.length) {
            return;
        }

        for(int i = startIndex;i<nums.length;i++) {
            if(i > startIndex && nums[i] == nums[i-1]){
                continue;
            }
            path.add(nums[i]);
            backTracking(nums,i + 1);
            path.removeLast();
        }
    }
}

全排列

一、思路

全排列的话[1,2]和[2,1]是不一样的排列,与组合不同,排列要求顺序,所以不需要使用startIndex,只需判断元素是否使用过,如果不判断同一条路径path上的结果是否使用过的话,会一直重复遍历同一个元素,例如:整数数组为[1,2,3,4],那么就会一直遍历1,有两种判断的办法:
一是used数组,二是path.contains(nums[i])

二、解题方法

used数组
在这里插入图片描述

  1. 递归函数的返回值以及参数:
    参数为nums整数数组,创建一个used布尔类型的数组,长度为nums数组的长度
private void permuteHelper(int[] nums)
  1. 回溯函数终止条件:
    到达叶子结点时,就把结果添加到结果集里,退出递归
if (path.size() == nums.length){
    result.add(new ArrayList<>(path));
    return;
}
  1. 单层搜索的过程:
    for循环从第一个元素开始遍历,used数组判断如果为true,则跳过这次循环,找数组里下一个元素,没有使用过的话,就把元素添加到path里,并让used数组为true,递归调用,回溯撤销元素,让used数组为false
for (int i = 0; i < nums.length; i++){
            if (used[i]){//使用过该元素
                continue;
            }
            used[i] = true;
            path.add(nums[i]);
            permuteHelper(nums);
            path.removeLast();
            used[i] = false;
        }

path.contains(nums[i]):
只要把for循环内的判断used数组改为path.contains(nums[i])即可,判断path有没有添加过该元素

if(path.contains(nums[i])){
    continue;
}

三、Code

used数组

class Solution {
    List<List<Integer>> result = new ArrayList<>();// 存放符合条件结果的集合
    LinkedList<Integer> path = new LinkedList<>();// 用来存放符合条件结果
    boolean[] used;
    public List<List<Integer>> permute(int[] nums) {
        if (nums.length == 0){
            return result;
        }
        used = new boolean[nums.length];
        permuteHelper(nums);
        return result;
    }

    private void permuteHelper(int[] nums){
        if (path.size() == nums.length){
            result.add(new ArrayList<>(path));
            return;
        }

        for (int i = 0; i < nums.length; i++){
            if (used[i]){//使用过该元素
                continue;
            }
            used[i] = true;
            path.add(nums[i]);
            permuteHelper(nums);
            path.removeLast();
            used[i] = false;
        }
    }
}

path.contains(nums[i])

class Solution {
    List<List<Integer>> result = new ArrayList<>();
	LinkedList<Integer> path = new LinkedList<>();

    public List<List<Integer>> permute(int[] nums) {
        if (nums.length == 0){
            return result;
        }
        backTracking(nums);
        return result;
    }

    public void backTracking (int[] nums) {
        if(path.size() == nums.length) {
            result.add(new ArrayList<>(path));
            return;
        }

        for(int i = 0;i<nums.length;i++) {
            if(path.contains(nums[i])){
                continue;
            }
            path.add(nums[i]);
            backTracking(nums);
            path.removeLast();
        }
    }
}

全排列II

一、思路

此题比上面的 全排列I 只多了一个数组元素可重复,与组合总和II、子集II是相同的都要进行先排序再去重,用到used数组判断同一层是否使用过,使用过的话,直接跳过这次循环
在这里插入图片描述

二、解题方法

与全排列I使用used数组的回溯三部曲大致相同,仅仅多了先对数组进行排序,随后判断同一层是否使用过,这个与组合总和II,子集II的通过used数组判断去重是一样的,在此省略

三、Code

class Solution {
    List<List<Integer>> result = new ArrayList<>();
	LinkedList<Integer> path = new LinkedList<>();
    boolean[] used;

    public List<List<Integer>> permuteUnique(int[] nums) {
        if (nums.length == 0){
            return result;
        }
        used = new boolean[nums.length];
        Arrays.sort(nums);
        backTracking(nums);
        return result;
    }

    private void backTracking(int[] nums){
        if (path.size() == nums.length){
            result.add(new ArrayList<>(path));
            return;
        }

        for (int i = 0; i < nums.length; i++){
            // 同一层用过直接跳过去重
            if(i>0 && nums[i] == nums[i-1] && used[i-1] == false){
                continue;
            }

            // 同一条结果路径上没有使用过
            if (used[i] == false){
                used[i] = true;
                path.add(nums[i]);
                backTracking(nums);
                path.removeLast();
                used[i] = false;
            }
            
        }
    }
}

总结

以上就是针对这道题的刷题笔记,通过这三篇博客,我们可以看到回溯算法在解决组合、排列等问题时的应用。其基本思想是通过递归和回溯来探索所有可能的解空间,并及时剪枝以避免重复计算,从而高效地求解问题。可以总结出如下回溯的套路:

去重:先对数组进行排序再判断同一层是否使用过相同元素if(i>0 && nums[i] == nums[i-1] && used[i-1] == false){ continue; }

排列和组合都是在叶子结点收集结果集,而子集则是在树的每一个结点都要进行收集结果集

希望本文对你理解和解决组合总和问题有所帮助!🌹

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于SSM的网上打印管理

摘要 进入二十一世纪以来&#xff0c;计算机技术蓬勃发展&#xff0c;人们的生活发生了许多变化。很多时候人们不需要亲力亲为的做一些事情&#xff0c;通过网络即可完成以往需要花费很多时间的操作&#xff0c;这可以提升人们的生活质量。计算机技术对人们生活的改变不仅仅包…

不会还有程序员不知道这几个宝藏学习平台吧?还不来码住!

咱作为程序员可谓是赶上了发展的时代啊&#xff01;前有ChatGPT&#xff0c;后有5G、物联网等等层出不穷。这正是咱大展身手、大赚一笔的好时候啊&#xff01;有多少人趁着风口期大干一笔&#xff0c;狠狠投入&#xff0c;最终不说是top级别&#xff0c;也至少是羡煞众人啊&…

最新AI智能系统ChatGPT网站源码V6.3版本,GPTs、AI绘画、AI换脸、垫图混图+(SparkAi系统搭建部署教程文档)

一、前言 SparkAi创作系统是基于ChatGPT进行开发的Ai智能问答系统和Midjourney绘画系统&#xff0c;支持OpenAI-GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美&#xff0c;那么如何搭建部署AI创作ChatGPT&#xff1f;小编这里写一个详细图文教程吧。已支持GPT…

[从0开始AIGC][Transformer相关]:Transformer中的激活函数:Relu、GELU、GLU、Swish

[从0开始AIGC][Transformer相关]&#xff1a;Transformer中的激活函数 文章目录 [从0开始AIGC][Transformer相关]&#xff1a;Transformer中的激活函数1. FFN 块 计算公式&#xff1f;2. GeLU 计算公式&#xff1f;3. Swish 计算公式&#xff1f;4. 使用 GLU 线性门控单元的 FF…

Redis基本配置及安装

Redis也叫Remote dictionary server,是一个开源的基于内存的数据存储系统。它可以用作数据库、缓存和消息队列等各种场景。它也是目前最热门的NoSQL数据库之一 以下是NoSQL的定义 随着互联网的快速发展&#xff0c;应用系统的访问量越来越大&#xff0c;数据库的性能瓶颈越来越…

自动驾驶中基于Transformer的传感器融合:研究综述

自动驾驶中基于Transformer的传感器融合&#xff1a;研究综述 论文链接&#xff1a;https://arxiv.org/pdf/2302.11481.pdf 调研链接&#xff1a;https://github.com/ApoorvRoboticist/Transformers-Sensor-Fusion 附赠自动驾驶学习资料和量产经验&#xff1a;链接 摘要 本…

解密JavaScript混淆:剖析JScrambler、JSFack、JShaman等五款常用加密工具

摘要 本篇技术博客将介绍五款常用且好用的在线JavaScript加密混淆工具&#xff0c;包括 jscrambler、JShaman、jsfack、freejsobfuscator 和 jjencode。通过对这些工具的功能及使用方法进行详细解析&#xff0c;帮助开发人员更好地保护和加密其 JavaScript 代码&#xff0c;提…

图的应用试题

01&#xff0e;任何一个无向连通图的最小生成树( )。 A.有一棵或多棵 B.只有一棵 C.一定有多棵 D.可能不存在 02.用Prim算法和Kruskal算法构造图的最小生成树&#xff0c;…

食物链(并查集) 维护权值写法,非常详细,适合新手服用

题目描述&#xff1a; 动物王国中有三类动物 A,B,C这三类动物的食物链构成了有趣的环形。 A 吃 B&#xff0c;B 吃 C&#xff0c;C吃 A。 现有 N 个动物&#xff0c;以 1∼N 编号。 每个动物都是 A,B,C 中的一种&#xff0c;但是我们并不知道它到底是哪一种。 有人用两种说法对…

YOLOv8全网独家改进: 小目标 | CAMixing:卷积-注意融合模块和多尺度提取能力 | 2024年4月最新成果

💡💡💡本文独家改进:CAMixingBlock更好的提取全局上下文信息和局部特征,包括两个部分:卷积-注意融合模块和多尺度前馈网络; 💡💡💡红外小目标实现涨点,只有几个像素的小目标识别率提升明显 💡💡💡如何跟YOLOv8结合:1)放在backbone后增强对全局和局部特…

[lesson02]C到C++的升级

C到C的升级 C与C的关系 C继承了所有的C特性C在C的基础上提供了更多的语法和特性C的设计目标是运行效率与开发效率的统一 C到C的升级 C更强调语言的实用性 所有的变量都可以在需要使用时再定义 int c 0; for (int i 1; i < 3; i) {for(int j 1; j < 3; j){c i * …

隐私计算实训营第六讲-隐语PIR介绍及开发实践

隐私计算实训营第六讲-隐语PIR介绍及开发实践 文章目录 隐私计算实训营第六讲-隐语PIR介绍及开发实践1.隐语实现PIR总体介绍1.1按服务器数量分类1.2按查询类型分类 2. Index PIR - SealPIR3. Keyword PIR - Labeled PSI4.隐语PIR功能分层5.隐语PIR后续计划PIR协议开发PIR调用框…

坦白局:PMP真的是智商税吗?

近些年报考PMP认证的学员越来越多&#xff0c;PMP全球持证人数已经突破百万了&#xff0c;据PMI统计&#xff0c;IT行业近50%人士都持有PMP证书&#xff0c;因此也有很多学员在思考&#xff0c;PMP持证人员这么多&#xff0c;PMP是不是都已经烂大街了&#xff1f;证书还有含金量…

【浅尝C++】STL第三弹=>list常用接口使用示例/list底层结构探索/list模拟实现代码详解

&#x1f3e0;专栏介绍&#xff1a;浅尝C专栏是用于记录C语法基础、STL及内存剖析等。 &#x1f3af;每日格言&#xff1a;每日努力一点点&#xff0c;技术变化看得见。 文章目录 list介绍list常用接口使用示例构造类函数迭代器属性与元素获取增删改操作 list底层结构探索list模…

2024年03月CCF-GESP编程能力等级认证Scratch图形化编程一级真题解析

本文收录于专栏《Scratch等级认证CCF-GESP真题解析》,专栏总目录・点这里 一、单选题(每题 3 分,共 30 分) 第1题 小杨的父母最近刚刚给他买了一块华为手表,他说手表上跑的是鸿蒙,这个 鸿蒙是?( )。 A、小程序 B、计时器 C、操作系统 D、神话人物 答案:C 第2题 …

golang语言系列:Web框架+路由 之 Gin

云原生学习路线导航页&#xff08;持续更新中&#xff09; 本文是golang语言学习系列&#xff0c;本篇对Gin框架的基本使用方法进行学习 1.Gin框架是什么 Gin 是一个 Go (Golang) 编写的轻量级 http web 框架&#xff0c;运行速度非常快&#xff0c;如果你是性能和高效的追求者…

HBase基础必备知识-Day1

HBase 简介 概述 HBase是Yahoo!公司开发的后来贡献给了Apache的一套开源的、分布式的、可扩展的、基于Hadoop的非关系型数据库(Non-Relational Database)&#xff0c;因此HBase并不支持SQL(几乎所有的非关系型数据库都不支持SQL)&#xff0c;而是提供了一套单独的命令和API操…

Redis高可用及持久化

文章目录 一、Redis高可用1、Redis高可用概述2、Redis高可用策略 二、Redis持久化1、Redis持久化的功能2、Redis持久化的两种方式2.1 RDB持久化2.2 AOF持久化&#xff08;append only file&#xff09; 3、RDB持久化3.1 触发条件3.1.1 手动触发3.1.2 自动触发3.1.2.1 配置方式3…

[Linux] 排查问题指令top/ps/netstat

在Linux下查看某个端口运行的指令 1. 首先通过netstat来查看端口对应的进程号 比如抓取端口53这个DNS服务的进程 netstat -tulnp | grep 53 可以看到53这个端口号对应的pid是720 2. 通过ps指令来对进程号执行的命令查询 ps aux | grep 720 可以看到pid为720这个进程对应的执…

聚道云助IT公司破解数据同步难,高效转型新利器!

客户介绍&#xff1a; 该公司是一家在信息技术行业具有丰富经验和良好声誉的公司。作为专业的软件服务提供商&#xff0c;他们致力于为客户提供全方位的解决方案和支持服务。公司秉持合规经营的原则&#xff0c;严格遵守相关法律法规&#xff0c;确保客户的数据安全和合法权益…