代码随想录算法训练营第29天(回溯算法05 | * 491.递增子序列 * 46.全排列 * 47.全排列 II

回溯算法part05

  • 491.递增子序列
    • 解题思路
      • 与` 90.子集II `不同的点
      • 回溯三部曲
  • 46.全排列
    • 解题思路
      • 遇到的难点
      • 思考
  • 47.全排列 II
    • 解题思路
      • 注意点
      • 拓展
      • 需要加深理解的点(需复习
  • 小总结

491.递增子序列

本题和大家刚做过的90.子集II非常像,但又很不一样,很容易掉坑里。
题目链接: 491.递增子序列
文章讲解: 491.递增子序列
视频讲解: 491.递增子序列

解题思路

90.子集II中我们是通过排序,再加一个标记数组来达到去重的目的。

而本题求自增子序列,是不能对原数组进行排序的,排完序的数组都是自增子序列了。

90.子集II不同的点

  1. 不能排序
  2. 不能用之前的used[]数组,而要用set,且set不需要跟着回溯,只负责本层里面取了哪些元素
  3. 需要判断每个path中元素个数是否大于等于2
  4. 需要判断每个path是否是递增的

回溯三部曲

  1. 递归函数参数:
  • 全局变量result和path
  • startIndex
  1. 终止条件:
    要遍历整个树,可以没有
  2. 单层遍历逻辑
  • 去重逻辑
  • 局部变量HashSet uset; 记录本层元素是否重复使用,新的一层uset都会重新定义(清空),所以要知道uset只负责本层!
    在这里插入图片描述
// uset用数组实现 效率高一些
class Solution {
    List<List<Integer>> result = new ArrayList<>();
    List<Integer> path = new LinkedList<>();

    public List<List<Integer>> findSubsequences(int[] nums) {
        backTracking(nums, 0);
        return result;
    }
    public void backTracking(int[] nums, int startIndex){
        if(path.size() >= 2){
            result.add(new ArrayList<>(path));
        }
        if(startIndex >= nums.length){ // 这个终止条件可以没有,因为我们要遍历整个树
            return;
        }
        int[] uset = new int[201];
        for(int i = startIndex; i <nums.length; i++){
            if(!path.isEmpty() && path.get(path.size() - 1) > nums[i] || uset[nums[i] + 100] == 1) continue; //注意是continue而不是break
            uset[nums[i] + 100] = 1;
            path.add(nums[i]);
            backTracking(nums, i + 1);
            path.remove(path.size() - 1);
        }
    }    
}
// // uset用set实现
class Solution {
    List<List<Integer>> result = new ArrayList<>();
    List<Integer> path = new LinkedList<>();

    public List<List<Integer>> findSubsequences(int[] nums) {
        backTracking(nums, 0);
        return result;
    }
    public void backTracking(int[] nums, int startIndex){
        if(path.size() >= 2){
            result.add(new ArrayList<>(path));
        }
        if(startIndex >= nums.length){ // 这个终止条件可以没有,因为我们要遍历整个树
            return;
        }
        HashSet<Integer> uset = new HashSet<>();
        for(int i = startIndex; i <nums.length; i++){
            if(!path.isEmpty() && path.get(path.size() - 1) > nums[i] || uset.contains(nums[i])) continue; //注意是continue而不是break
            uset.add(nums[i]);
            path.add(nums[i]);
            backTracking(nums, i + 1);
            path.removeLast();
        }
    }
}

46.全排列

本题重点感受一下,排列问题 与 组合问题,组合总和,子集问题的区别。 为什么排列问题不用 startIndex
题目链接: 46.全排列
文章讲解: 46.全排列
视频讲解: 46.全排列

解题思路

不需要i = startIndex控制for循环开始位置,每次从i = 0开始
需要判断当前元素是否已经取过

遇到的难点

如何不重复取自身元素:used数组,其实就是记录此时path里都有哪些元素使用了,一个排列里一个元素只能使用一次

思考

这道题的used数组和之前题目中的used数组作用的不同

  • 这道题的used数组:记录此时path里都有哪些元素使用了
  • 之前题目中的used数组:树层上对组合去重,一般要先将数组排序
    在这里插入图片描述
// 解法1:通过判断path中是否存在数字,排除已经选择的数字
// 感觉这种比解法2好理解
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;
        backtrack(nums, path);
        return result;
    }
    public void backtrack(int[] nums, LinkedList<Integer> path) {
        if (path.size() == nums.length) {
            result.add(new ArrayList<>(path));
        }
        for (int i =0; i < nums.length; i++) {
            // 如果path中已有,则跳过
            if (path.contains(nums[i])) {
                continue;
            } 
            path.add(nums[i]);
            backtrack(nums, path);
            path.removeLast();
        }
    }
}
// 法2:通过used判断是否path中已取过当前数字
class Solution {
    List<List<Integer>> result = new ArrayList<>();
    List<Integer> path = new LinkedList<>();
    boolean[] used;
    public List<List<Integer>> permute(int[] nums) {
        used = new boolean[nums.length];
        backTracking(nums);
        return result;
    }
    public void backTracking(int[] nums){
        if(path.size() == nums.length){
            result.add(new ArrayList<>(path));
        }
        for(int i = 0; i < nums.length; i++){
            if(used[i]){
                continue;
            }
            used[i] = true;
            path.add(nums[i]);
            backTracking(nums);
            used[i] = false;
            path.removeLast();

        }
    }
}

47.全排列 II

本题 就是我们讲过的40.组合总和II去重逻辑 和46.全排列的结合,可以先自己做一下,然后重点看一下 文章中 我讲的拓展内容。 used[i - 1] == true 也行,used[i - 1] == false 也行
题目链接: 47.全排列 II
文章讲解: 47.全排列 II
视频讲解: 47.全排列 II

解题思路

40.组合总和II去重逻辑 和46.全排列的结合

注意点

nums数组排序

Arrays.sort(nums);

树层去重

if(i > 0 && nums[i - 1] == nums[i] && used[i - 1] == false) continue;  // 树层去重

取过的元素不再重复取

if(used[i] == true) continue;    // 取过的数标记为1

拓展

去重代码中,如果改成 used[i - 1] == true, 也是正确的!
这是为什么呢,就是上面我刚说的,如果要对树层中前一位去重,就用used[i - 1] == false,如果要对树枝前一位去重用used[i - 1] == true。
对于排列问题,树层上去重和树枝上去重,都是可以的,但是树层上去重效率更高!
树枝去重图例
在这里插入图片描述

需要加深理解的点(需复习

  • 树层去重和树枝去重
  • used数组在不同题中的作用
  • 为什么这道题的used数组需要作为参数参与递归结合40.组合总和II90.子集II进行思考
  • 491.递增子序列中的uset
    在这里插入图片描述

小总结

491.递增子序列 不能用之前的used[]数组,而要用set,且set不需要跟着回溯,只负责本层里面取了哪些元素 used数组不需要回溯,不需要放在递归参数里面
46.全排列 used数组,其实就是记录此时path里都有哪些元素使用了,一个排列里一个元素只能使用一次 used数组要回溯,要放在递归参数里面
47.全排列 II used数组:去重+取过的元素不再重复取 used数组要回溯,要放在递归参数里面

class Solution {
    List<List<Integer>> result = new ArrayList<>();
    List<Integer> path = new LinkedList<>();
    boolean[] used;
    public List<List<Integer>> permuteUnique(int[] nums) {
        used = new boolean[nums.length];
        Arrays.sort(nums);
        backTracking(nums, used);
        return result;
    }
    public void backTracking(int[] nums, boolean[] used){
        if(path.size() == nums.length){
            result.add(new ArrayList<>(path));
            return;
        }
        for (int i =0; i < nums.length; i++) {
            // 树层去重
            if(i > 0 && nums[i - 1] == nums[i] && used[i - 1] == false) continue;  // 树层去重
            if(used[i] == true) continue;    // 取过的数标记为1
            used[i] = true;
            path.add(nums[i]);
            backTracking(nums, used);
            used[i] = false;
            path.removeLast();
        }
    }
}

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

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

相关文章

服务器故障处理 | 浪潮SA5212H5服务器排查出现故障的内存条

服务器故障处理 | 浪潮SA5212H5服务器排查出现故障的内存条 浪潮SA5212H5服务器管理界面如下:    这个型号的浪潮服务器很特殊,没有内存条的硬件信息,也没有具体哪个位置的内存条出现故障,接下来需要去操作系统层面查看具体的信息。 为了摸清是哪些内存出了问题,…

第一篇【传奇开心果短博文系列】鸿蒙开发技术点案例示例:从helloworld开始理解鸿蒙开发ArkTS编程思路

传奇开心果短博文系列 系列短博文目录鸿蒙开发技术点案例示例系列 短博文目录一、前言二、初步解读鸿蒙的helloworld三、进一步深入解读理解 系列短博文目录 鸿蒙开发技术点案例示例系列 短博文目录 一、前言 从掰碎了揉烂了详细注释解读helloworld开始&#xff0c;理解Ark…

【电子通识】传统网络变压器原理与生产流程

网络变压器也称为网络隔离变压器。传统的网络变压器贴片器件大概长的都类似以下这样&#xff1a; 在网络接口上所起的作用主要有信号耦合、高压隔离、阻抗匹配、电磁干扰抑制作用。它主要用在网络交换机、路由器、网卡等产品。 做为数据传输时使用网络变压器可以达到以下效果&a…

VS2022联合Qt5开发学习10(QT5.12.3联合VTK在VS2022上开发医学图像项目4——ScrollBar控制对比度、切面位置)

这篇博文是接着VS2022联合Qt5开发学习7&#xff08;QT5.12.3联合VTK在VS2022上开发医学图像项目2——十字叉标注&#xff09;-CSDN博客这篇博文延伸开发医学图像的显示渲染相关项目&#xff0c;主要介绍的是在之前显示的图像上增加滑块控制。 用到的内容有&#xff1a; VS2022…

雪花算法 Nginx

雪花算法介绍 SnowFlake 算法&#xff0c;是 Twitter 开源的分布式 id 生成算法。其核心思想就是&#xff1a;使用一个 64 bit 的 long 型的数字作为全局唯一 id 1位&#xff0c;不用。二进制中最高位为1的都是负数&#xff0c;但是生成的id都是正数&#xff0c;所以这个最高位…

深入理解badblocks

文章目录 一、概述二、安装2.1、源码编译安装2.2、命令行安装2.3、安装确认 三、重要参数详解3.1、查询支持的参数3.2、参数说明 四、实例4.1、全面扫描4.2、破坏性写入并修复4.3、非破坏性写入测试 五、实现原理六、注意事项 团队博客: 汽车电子社区 一、概述 badblocks命令是…

【码农新闻】原来这就是网络,小霸王是我儿时快乐源泉之一,不知这里有没有你儿时玩过的游戏机呢......

目录 【码农新闻】原来这就是网络&#xff0c;小霸王是我儿时快乐源泉之一,不知这里有没有你儿时玩过的游戏机呢...... 【何同学】我毕业了&#xff01;&#xff01;原来这就是网络承载童年的游戏机&#xff0c;已停产&#xff01;但我在 GitHub 找到了它们国货正当潮-那些你所…

蓝桥杯省赛无忧 编程12 四元组问题

#include <bits/stdc.h> using namespace std; bool FoursNumberFind(vector<int>& nums) {stack<int> st;int n nums.size(), k INT_MIN, INF INT_MAX;//min_r[i] min(nums[r]), i < r < n。//表示第i个数&#xff08;不包括第i个数&#xff…

2024年电工(初级)证考试题库及电工(初级)试题解析

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年电工&#xff08;初级&#xff09;证考试题库及电工&#xff08;初级&#xff09;试题解析是安全生产模拟考试一点通结合&#xff08;安监局&#xff09;特种作业人员操作证考试大纲和&#xff08;质检局&#…

k8s---安全机制

k8s的安全机制&#xff0c;分布式集群管理工具&#xff0c;就是容器编排。安全机制的核心&#xff1a;APIserver。为整个集群内部通信的中介&#xff0c;也是外控控制的入口。所有的机制都是围绕apiserver来进行设计&#xff1a; 请求api资源&#xff1a; 1、认证 2、鉴权 …

陈酿过程中的物质转化与品质改善

陈酿是酿酒过程中至关重要的环节&#xff0c;它能够改善酒的品质和口感&#xff0c;使酒更加醇厚、芳香。云仓酒庄的豪迈白酒在陈酿过程中&#xff0c;物质转化与品质改善方面表现得尤为杰出。 首先&#xff0c;陈酿能够促进酒中物质的转化。在长时间的陈酿过程中&#xff0c;酒…

遇到IP禁令怎么办?别慌,解决方法在这

相信很多人遇到过IP禁令&#xff1a;比如你在访问社交媒体、搜索引擎或电子商务网站时会被限制访问&#xff0c;又或者你的的账号莫名被封&#xff0c;这些由于网络上的种种限制我们经常会遭遇IP被封的情况&#xff0c;导致无法使用继续进行网络行动。在本文中&#xff0c;我们…

Docker容器引擎(3)

目录 一.Docker 镜像的创建 1&#xff0e;基于现有镜像创建 2&#xff0e;基于本地模板创建 3.基于Dockerfile创建&#xff1a; Dockerfile 操作常用的指令&#xff1a; ADD 和 COPY 的区别&#xff1f; CMD 和 ENTRYPOINT 的区别&#xff1f; 容器启动命令的优先级 如…

c++day2

计算矩形的面积 #include <iostream> using namespace std; class Rect {int width;int heigh; public:void init(int w, int h){width w;heigh h;}void set_w(int w){width w;}void set_h(int h){heigh h;}void show(){cout << "该矩形面积:" <…

ASP.NET Core NE8实现HTTP Upgrade和HTTP CONNECT代理服务器

看到一个文章[Go] 不到 100 行代码实现一个支持 CONNECT 动词的 HTTP 服务器 在NET8中如何实现 创建项目为MiniApi 编辑Program.cs文件。 var builder WebApplication.CreateSlimBuilder(args);var app builder.Build();// 将HTTP请求通过协议升级机制转为远程TCP请求&…

可视化智慧水电站EasyCVR视频智能监控系统方案设计与技术应用介绍

一、背景需求 水电站作为国家重要的能源基地&#xff0c;其安全运行对于保障能源供应和社会稳定具有重要意义。然而&#xff0c;传统的人工监控方式存在着诸多问题&#xff0c;如人力成本高、监控范围有限、反应不及时等。因此&#xff0c;水电站急需引进一种先进的视频智能监…

开始学习Vue2(组件的生命周期和数据共享)

一、组件的生命周期 1. 生命周期 & 生命周期函数 生命周期&#xff08;Life Cycle&#xff09;是指一个组件从创建 -> 运行 -> 销毁的整个阶段&#xff0c;强调的是一个时间段。 生命周期函数&#xff1a;是由 vue 框架提供的内置函数&#xff0c;会伴随着 组件…

解读Android进程优先级ADJ算法

本文基于原生Android 9.0源码来解读进程优先级原理,基于篇幅考虑会精炼部分代码 一、概述 1.1 进程 Android框架对进程创建与管理进行了封装,对于APP开发者只需知道Android四大组件的使用。当Activity, Service, ContentProvider, BroadcastReceiver任一组件启动时,当其所…

[极客大挑战 2019]Upload1

直接上传php一句话木马&#xff0c;提示要上传image 把文件名改成gif并加上gif文件头后&#xff0c;绕过了对image类型的检测&#xff0c;但是提示文件内含有<?&#xff0c;且bp抓包后改回php也会被检测 那我们考虑使用js执行php代码 <script languagephp>eval($_PO…

使用 LinkAi 打造自己的知识库和数字人

其他系列文章导航 Java基础合集数据结构与算法合集 设计模式合集 多线程合集 分布式合集 ES合集 文章目录 其他系列文章导航 文章目录 前言 一、LinkAi 介绍 二、文档库 2.1 创建知识库 2.2 配置知识库 2.3 Ai配置 2.4 导入文档 2.5 接入微信 三、扩展 四、总结…