【代码随想录训练营】【Day29】第七章|回溯算法|491.递增子序列|46.全排列|47.全排列 II

news2024/9/23 23:32:26

递增子序列

题目详细:LeetCode.491

注意这道题求的是子序列,而不是子数组,子数组要求其数组是原数组的子集,且元素是连续的,而子序列只需要保证至少有两个元素即可,不需要关系元素是否连续。

所以一开始我确定了递归的结束条件是 startIndex == nums.length 之后,在循环过程中增加了nums[i] - nums[i - 1] < 0 来判断序列是否递增,如果递增则直接break,这样的做法是错误的,因为我们要求的是所有的可能的子序列,所以不能够直接跳出循环,还需要取后续树上的节点。

同时nums[i] - nums[i - 1] < 0 ,判断的是在原数组中连续的元素是否递增,但是子序列并不要求元素是连续的,所以这一判断条件本身也是错的,应该改为nums[i] < path.getLast(),用当前数值来比较上一个递增序列的尾数来判断是否是递增子序列。

在求子序列的同时,还要注意去重,一开始由于收到前面练习题的启发,我利用布尔数组used来进行去重,但发现在这道题中并不适用,因为之前利用used去重,都需要先将原数组进行排序;但在这道题中,排序之后,就得不到与原数组一样的递增子序列,变成从头到尾分割递增序例了,这是互相矛盾的。

所以正确的去重方式,应该是在每一层循环中都新建一个哈希表,用于记录在树形结构的同一层中(循环过程中)已经出现过的数字,假如该数值已经被添加到某一个子序列中,那么后面则不能再添加到该序列中,否则会出现重复的子序列。

这里我们在每一次递归中都新建一个哈希Set来记录在本次循环中出现过的数字,如果出现过,则跳过该数字,继续往后寻找递增的数字组成递增子序列。

以上仅是我的解题思考过程,以及踩过的一些坑和后续看完题解后,对思路的纠正过程,详细的题解可查阅:《代码随想录》— 递增子序列

Java解法(递归,回溯):

class Solution {
    List<List<Integer>> ans = new ArrayList<>();
    Deque<Integer> path = new ArrayDeque<>();

    public void backTrack(int[] nums, int startIndex){
        if(path.size() > 1){
            ans.add(new ArrayList<>(path));
        }
        if(startIndex == nums.length){
            return;
        }
        Set<Integer> set = new HashSet<>();
        for(int i = startIndex; i < nums.length; i++){
            if((!path.isEmpty() && nums[i] < path.getLast()) || set.contains(nums[i])){
                continue;
            }
            set.add(nums[i]);
            path.offer(nums[i]);
            backTrack(nums, i + 1);
            path.removeLast();
        }
    }

    public List<List<Integer>> findSubsequences(int[] nums) {
        this.backTrack(nums, 0);
        return this.ans;
    }
}

全排列

题目详细:LeetCode.46

全排列转化成树形结构之后,思路就非常明辽了:
在这里插入图片描述
通过树形结构的叶子节点,也就是递归的结束条件可知,就是当 nums.length == 0 时,即可得到一个路径上的排列结果。

与之前做的练习题不同,在同一层中,每一个的数字都可以被访问到,也没有涉及到分割问题,所以在递归参数中可以不需要定义 startIndex 来控制从哪个下标开始访问。

所以我们可以模拟对该树形结构进行深度优先遍历得到下面的解法:

Java解法(递归,回溯,模拟):

import java.nio.*;

class Solution {
    List<List<Integer>> ans = new ArrayList<>();
    Deque<Integer> path = new ArrayDeque<>();

    public int[] linkNums(int[] nums_l, int[] nums_r){
        IntBuffer intbuffer = IntBuffer.allocate(nums_l.length + nums_r.length);
        intbuffer.put(nums_l);
        intbuffer.put(nums_r);
        return intbuffer.array();
    }

    public int[] linkBetNums(int[] nums, int index){
        int[] nums_l = Arrays.copyOfRange(nums, 0, index);
        int[] nums_r = Arrays.copyOfRange(nums, index + 1, nums.length);
        return this.linkNums(nums_l, nums_r);
    }
    
    public void backTrack(int[] nums){
        if(nums.length == 0){
            ans.add(new ArrayList<>(path));
            return;
        }

        for(int i = 0; i < nums.length; i++){
            path.offer(nums[i]);
            backTrack(this.linkBetNums(nums, i));
            path.removeLast();
        }
    }

    public List<List<Integer>> permute(int[] nums) {
        this.backTrack(nums);
        return this.ans;
    }
}

不过这样的模拟解法,看起来还是蛮多代码的,这是因为我们为了使数组nums 最后能够通过递归结束条件 nums.length == 0 ,所以多定义了两个方法,使得在每一次递归中都传递的分割后的新数组来进行操作。

但通过排列结果我们可以发现,其实可以不用这样做,递归的结束条件还可以是 path.length() == nums.length,只不过我们需要额外定义一个布尔数组used ,来标识我们每一次递归中(树的深度优先遍历)那些已经被排列的数字的下标,避免重复在排列中出现。

Java解法(递归,回溯,哈希):

class Solution {
    List<List<Integer>> ans = new ArrayList<>();
    Deque<Integer> path = new ArrayDeque<>();
    
    public void backTrack(int[] nums, boolean[] used){
        if(nums.length == path.size()){
            ans.add(new ArrayList<>(path));
            return;
        }

        for(int i = 0; i < nums.length; i++){
            if(used[i]){
                continue;
            }
            path.offer(nums[i]);
            used[i] = !used[i];
            backTrack(nums, used);
            path.removeLast();
            used[i] = !used[i];
        }
    }

    public List<List<Integer>> permute(int[] nums) {
        boolean[] used = new boolean[nums.length];
        this.backTrack(nums, used);
        return this.ans;
    }
}

全排列 II

题目详细:LeetCode.47

46.全排列 中已经详细讲解了排列问题的写法,在 40.组合总和II90.子集II中又详细讲解了去重的写法,话不多说,先画个图:

在这里插入图片描述

通过树形结构可知,这道题就是在上一题的基础上,加上去重的操作:

  • 经过之前的题目中的去重练习,我们可以利用布尔数组used来跳过那些相同且已经被回溯的数字(即已经得到该数字开头的所有全排列结果),来进行去重
  • 而且对于组合问题和排列问题进行去重,一定要对元素进行排序,这样可以方便地得知相邻的数字是否是重复的
  • 去重的具体思路如下:
    • 在宽度遍历到下标i>0的数字时,就与之前一个数字做比较,如果两者相等nums[i] == nums[i - 1]且前一个数字已经被回溯used[i-1] == false,说明该数字开头的所有排列结果都已得到并添加到结果集中
    • 直接continue跳过相同的数字,继续循环直到遇到不相同的数字,进行递归继续收集其全排列结果。

Java解法(递归,回溯,哈希):

class Solution {
    List<List<Integer>> ans = new ArrayList<>();
    Deque<Integer> path = new ArrayDeque<>();
    
    public void backTrack(int[] nums, boolean[] used){
        if(nums.length == path.size()){
            ans.add(new ArrayList<>(path));
            return;
        }

        for(int i = 0; i < nums.length; i++){
        	// used[i]:跳过在深度中已排列过的数字的下标
        	// i > 0 && nums[i] == nums[i - 1] && !used[i - 1]:跳过在宽度中已排列过的数字
            if(used[i] || (i > 0 && nums[i] == nums[i - 1] && !used[i - 1])){
                continue;
            }
            path.offer(nums[i]);
            used[i] = !used[i];
            backTrack(nums, used);
            path.removeLast();
            used[i] = !used[i];
        }
    }

    public List<List<Integer>> permuteUnique(int[] nums) {
        Arrays.sort(nums);
        boolean[] used = new boolean[nums.length];
        this.backTrack(nums, used);
        return this.ans;
    }
}

做了这几道练习题之后,不难发现:

  • ▲组合问题和排列问题是在树形结构的叶子节点上收集结果,而子集问题就是取树上所有节点的结果。
  • 有回溯必有递归,回溯的条件即是递归结束的条件
  • 树形结构的宽度,与循环的次数有关
  • 树形结构的深度,与递归的次数有关
  • 递归的深度,相当于循环的嵌套层数
  • ▲递归的深度由结束条件决定,和树的深度由树的叶子节点决定一样的道理,所以也可以说在回溯算法中,树的深度与递归的结束条件有关,换而言之,要想知道什么时候将路径上的结果加入结果集,只需要关心递归的结束条件即可
  • ▲循环的次数由循环的起始下标和循环体中的逻辑决定,主要处理的树形结构中同一层的元素访问逻辑,所以对结果去重主要是在循环体内,递归前进行。

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

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

相关文章

测试人员如何在测试环境数据库批量生成测试数据?方案分享

测试人员为了测试某个特定场景&#xff0c;往往需要在测试环境数据库中插入特定的测试数据来满足需求&#xff1b;性能测试时&#xff0c;常需要在测试环境生成大量可用测试数据来支持性能测试&#xff1b;建设持续集成持续交付体系时&#xff0c;我们往往也需要在测试环境生成…

【网络】套接字 -- TCP

&#x1f941;作者&#xff1a; 华丞臧. &#x1f4d5;​​​​专栏&#xff1a;【网络】 各位读者老爷如果觉得博主写的不错&#xff0c;请诸位多多支持(点赞收藏关注)。如果有错误的地方&#xff0c;欢迎在评论区指出。 推荐一款刷题网站 &#x1f449; LeetCode刷题网站 文章…

记录一次nginx转发代理skywalking白屏 以及nginx鉴权配置

上nginx代码 #user nobody; worker_processes 1; #error_log logs/error.log; #error_log logs/error.log notice; #error_log logs/error.log info; #pid logs/nginx.pid; events { worker_connections 1024; } http { include mime.types; …

【2023】某python语言程序设计跟学第二周内容

本文说明&#xff1a; 案例内容为北理工python语言程序设计课程&#xff0c;如有不妥请联系&#xff01; 目录蟒蛇绘制案例&#xff1a;执行结果&#xff1a;代码分析&#xff1a;举一反三&#xff1a;绘制一个五角星图案执行结果&#xff1a;turtle库根据案例简单说明&#xf…

linux(Centos)安装docker

官网地址&#xff1a;Install Docker Engine on CentOS 首先检查linux系统版本及内核&#xff1a; 安装docker要求系统版本至少为7.x版本&#xff0c;内核至少为3.8以上 cat /etc/redhat-release # 查看系统版本号uname -r #查看linux系统内核 检查系统是否能连上外网&#…

3.基于Label studio的训练数据标注指南:文本分类任务

文本分类任务Label Studio使用指南 1.基于Label studio的训练数据标注指南&#xff1a;信息抽取&#xff08;实体关系抽取&#xff09;、文本分类等 2.基于Label studio的训练数据标注指南&#xff1a;&#xff08;智能文档&#xff09;文档抽取任务、PDF、表格、图片抽取标注等…

NJU数电实验-1

实验一 选择器 2选1多路选择器 逻辑表达式&#xff1a;y(∼s&a)∣(s&b)y(\sim s\&a)|(s\&b)y(∼s&a)∣(s&b) 逻辑电路&#xff1a; 数据流建模 数据流建模主要是通过连续赋值语句 assign 来描述电路的功能 module m_mux21(a,b,s,y);input a,b,s;…

这是一篇很好的互动式文章,Framer Motion 布局动画

重现framer的神奇布局动画的指南。 到目前为止&#xff0c;我最喜欢 Framer Motion 的部分是它神奇的布局动画–将 layout prop 拍在任何运动组件上&#xff0c;看着该组件从页面的一个部分无缝过渡到下一个部分。 <motion.div layout /> 在这篇文章中&#xff0c;我们…

【测试岗】那个准点下班的人,比我先升职了...

前言 陈双喜最近心态很崩。和他同期一道进公司的陈琪又升了一级&#xff0c;可是明明大家在进公司时&#xff0c;陈琪不论是学历还是工作经验&#xff0c;样样都不如自己&#xff0c;眼下不过短短的两年时间便一跃在自己的职级之上&#xff0c;这着实让他有几分不甘心。 程双…

linux常用命令介绍 03 篇——常用的文本处理工具之grep和cut(以及部分正则使用)

linux常用命令介绍 03 篇——常用的文本处理工具之grep和cut&#xff08;以及部分正则使用&#xff09;1 常用命令01篇 和 02篇1.1 Linux命令01篇——Linux解压缩文件常用命令1.2 Linux命令02篇——linux日常常用命令介绍2. 正则表达式2.1 基本定义2.2 正则中常用的元字符3. gr…

【python】异常详解

注&#xff1a;最后有面试挑战&#xff0c;看看自己掌握了吗 文章目录错误分类捕捉异常实例finally的使用捕捉特定异常抛出异常用户自定义异常&#x1f338;I could be bounded in a nutshell and count myself a king of infinite space. 特别鸣谢&#xff1a;木芯工作室 、I…

项目质量管理有哪些不同阶段?其中“质量“指的是什么?

项目质量管理是指在整个项目中管理和保持质量的过程。 "质量 "不是意味着 "完美"&#xff0c;通常更多的是指在整个项目中确保质量的一致性。然而&#xff0c;"质量 "的确切含义取决于客户或利益相关者对项目的需求&#xff0c;因此在每个项目可…

Ubuntu开机自动挂载硬盘

查看挂载信息&#xff0c;命令台输入 df -h能够看到/dev/nvme0n1p2是我们要挂在的硬盘&#xff0c;其路径是/media/lkzcswq/Data 找到要挂载磁盘的UUID sudo blkid /dev/nvme0n1p2观察到这个磁盘的UUID为72922DF0922DBA0D&#xff0c;type为ntfs 4. 编辑/etc/fstab文件 #如…

【服务器数据恢复】VMware虚拟机下的SQL Server数据库数据恢复案例

服务器数据恢复环境&#xff1a; 一台某品牌PowerEdge系列服务器和一台PowerVault系列存储&#xff0c;上层是ESXI虚拟机文件&#xff0c;虚拟机中运行SQL Server数据库。 服务器故障&#xff1a; 机房非正常断电导致虚拟机无法启动。管理员检查虚拟机发现虚拟机配置文件丢失&…

一、Java概述

一、Java概述 1.1 版本 Java SE 标准版&#xff08;核心版本&#xff09;&#xff0c;主要包含Java最核心的库包括&#xff1a;集合&#xff0c;IO&#xff0c;数据库连接、网络编程等 Java EE 企业版&#xff0c;主要用于开发&#xff0c;装配&#xff0c;部署企业级应用包括…

工业机器人编程调试怎么学

很多人觉得工业机器人很难学学&#xff0c;实际上机器人涉及的知识远比PLC要少。现简单说明一下初学者学习工业机器人编程调试的流程&#xff0c;以AUBO机器人为例&#xff1a; 首先我们需要知道工业机器人的调试学起来不难&#xff0c;远比编程更简单&#xff0c;示教器上的编…

基于信息间隙决策理论的碳捕集电厂调度(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

Golang Map原理(底层结构、查找/新增/删除、扩缩容)

参考&#xff1a; 解剖Go语言map底层实现Go语言核心手册-3.字典 一、Go Map底层结构&#xff1a; Go map的底层实现是一个哈希表&#xff08;数组 链表&#xff09;&#xff0c;使用拉链法消除哈希冲突&#xff0c;因此实现map的过程实际上就是实现哈希表的过程。 先来看下…

react hooks学习记录

react hook学习记录1.什么是hooks2.State Hook3.Effect Hook4.Ref Hook1.什么是hooks (1). Hook是React 16.8.0版本增加的新特性/新语法 (2). 可以让你在函数组件中使用 state 以及其他的 React 特性 貌似现在更多的也是使用函数式组件的了&#xff0c;重要 2.State Hook imp…

Linux系统安装:Zookeeper

目录 Zookeeper的安装 1、环境准备 2、上传 3、解压文件到opt/zookeeper目下 4、安装完后进入zookeeper&#xff0c;找到conf目录 5、复制zoo_sample.cfg 6、编辑zoo.cfg 7、复制一份会话&#xff0c;进入zookeeper安装目录&#xff0c;创建一个文件夹zkdata&#xff0…