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

回溯章节理论基础:

https://programmercarl.com/%E5%9B%9E%E6%BA%AF%E7%AE%97%E6%B3%95%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%80.html

491.递增子序列

题目链接:https://leetcode.cn/problems/non-decreasing-subsequences/

思路:

本题求自增子序列,是不能对原数组进行排序的,排完序的数组都是自增子序列了。所以不能使用之前的去重逻辑!

本题给出的示例,还是一个有序数组 [4, 6, 7, 7],这更容易误导大家按照排序的思路去做了。

为了有鲜明的对比,我用[4, 7, 6, 7]这个数组来举例,抽象为树形结构如图:

在这里插入图片描述
本题求子序列,很明显一个元素不能重复使用,所以需要startIndex,调整下一层递归的起始位置。

本题其实类似求子集问题,也是要遍历树形结构找每一个节点,所以和回溯算法:求子集问题!一样,可以不加终止条件,startIndex每次都会加1,并不会无限递归。

但本题收集结果有所不同,题目要求递增子序列大小至少为2,所以需要加个判断。

同时,同一父节点下的同层上使用过的元素就不能再使用了。所以单层中,我们使用了一个hashset,每次都add一下。但是后面,没有对应的remove操作!

这里是需要注意的地方,hashset是记录本层元素是否重复使用,新的一层set都会重新定义(清空),所以要知道set只负责本层!

class Solution {List<List<Integer>> result = new ArrayList<>();List<Integer> paths = new ArrayList<>();public List<List<Integer>> findSubsequences(int[] nums) {backtracking(nums,0);return result;}public void backtracking(int[] nums, int startIndex){if(paths.size() >= 2)result.add(new ArrayList<>(paths));HashSet<Integer>set = new HashSet<>();for(int i=startIndex; i < nums.length ; i++){if(paths.size() >0 && paths.get(paths.size() - 1) > nums[i]  || set.contains(nums[i])){continue;}paths.add(nums[i]);set.add(nums[i]);backtracking(nums, i+1);paths.removeLast();}}
}

时间复杂度: O(n * 2^n)
空间复杂度: O(n)

46.全排列

题目链接:https://leetcode.cn/problems/permutations/

思路:

给定一个 没有重复 数字的序列,返回其所有可能的全排列。

此时我们已经学习了77.组合问题、 131.分割回文串 和78.子集问题 ,接下来看一看排列问题。

我以[1,2,3]为例,抽象成树形结构如下:

在这里插入图片描述
首先排列是有序的,也就是说 [1,2] 和 [2,1] 是两个集合,这和之前分析的子集以及组合所不同的地方。

可以看出元素1在[1,2]中已经使用过了,但是在[2,1]中还要在使用一次1,所以处理排列问题就不用使用startIndex了。

但排列问题需要一个used数组,标记已经选择的元素,如上图的橘黄色部分所示。

终止情况:当收集元素的数组path的大小达到和nums数组一样大的时候,说明找到了一个全排列,也表示到达了叶子节点。

而used数组,其实就是记录此时path里都有哪些元素使用了,一个排列里一个元素只能使用一次。

class Solution {List<List<Integer>> result = new ArrayList<>();List<Integer> paths = new ArrayList<>();boolean[] used;public List<List<Integer>> permute(int[] nums) {used = new boolean[nums.length];Arrays.fill(used,false);backTracking(nums);return result;}public void backTracking(int[] nums){if(paths.size() == nums.length){result.add(new ArrayList<>(paths));return ;}for(int i=0;i<nums.length;i++){if(used[i]) continue;   // 如果使用过,就跳过used[i] = true;paths.add(nums[i]);backTracking(nums);paths.removeLast();used[i] = false;}}
}

时间复杂度: O(n!)
空间复杂度: O(n)

47.全排列 II

题目链接:https://leetcode.cn/problems/permutations-ii/

思路:

这道题目和46.全排列 的区别在与给定一个可包含重复数字的序列,要返回所有不重复的全排列。

这里又涉及到去重了。去重的套路和40.组合总和II 、90.子集II 里的组合问题和子集问题的去重套路一样。

同时,去重一定要对元素进行排序,这样我们才方便通过相邻的节点来判断是否重复使用了。

我以示例中的 [1,1,2]为例 (为了方便举例,已经排序)抽象为一棵树,去重过程如图:

在这里插入图片描述
图中我们对同一树层,前一位(也就是nums[i-1])如果使用过,那么就进行去重。

在46.全排列 中已经详细讲解了排列问题的写法,在40.组合总和II 、90.子集II 中详细讲解了去重的写法,所以代码也就很好写出来了。

class Solution {List<List<Integer>> result = new ArrayList<>();List<Integer> paths = new ArrayList<>();boolean[] used2;public List<List<Integer>> permuteUnique(int[] nums) {Arrays.sort(nums);used2 = new boolean[nums.length];Arrays.fill(used2,false);backtracking(nums,used2);return result;}public void backtracking(int[] nums, boolean[] used){if(paths.size() == nums.length){result.add(new ArrayList<>(paths));return ;}for(int i=0;i<nums.length;i++){if(used[i]) continue;   // 如果使用过,就跳过// 如果在同一树层使用过,就跳过if(i>0 && nums[i] == nums[i-1] && used[i-1] == false){continue;}used[i] = true;paths.add(nums[i]);backtracking(nums,used);paths.removeLast();used[i] = false;}}
}

时间复杂度: O(n! * n)
空间复杂度: O(n)

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

关于域名递归解析服务的问题

域名递归解析服务是互联网基础设施的重要组成部分&#xff0c;它允许用户通过域名来访问网站或应用程序。然而&#xff0c;在某些情况下&#xff0c;域名递归解析服务可能会出现问题&#xff0c;导致用户无法正常访问网站或应用程序。本文将探讨域名递归解析服务可能面临的问题…

一文带你搞定搭建自己的组件库Rollup

一文带你搞定搭建自己的组件库(rollup.js) 目前Vue和React都是采用rollup.js进行打包,我们在公司开发的时候也可以用rollup搭建一个自己的组件库放到你的项目中,简化项目的结构项目性能。 接下来我将带你使用rollup从0——1搭建一个在vue中使用的组件库 开发前准备 我的开发…

提示由于找不到msvcp120dll无法继续执行此代码怎么办

在计算机系统中&#xff0c;MSVCP120.dll是一个至关重要的动态链接库文件&#xff0c;它是Microsoft Visual C Redistributable Package的一部分&#xff0c;对于许多基于Windows的应用程序运行至关重要。当系统提示“msvcp120dll丢失”时&#xff0c;意味着该文件可能由于误删…

c++设计模式之装饰器模式

作用 为现有类增加功能 案例说明 class Car { public:virtual void show()0; };class Bmw:public Car { public:void show(){cout<<"宝马汽车>>"<<endl;} };class Audi:public Car { public:void show(){cout<<"奥迪汽车>>&q…

【Unity】QFramework通用背包系统优化:使用Odin优化编辑器

前言 在学习凉鞋老师的课程《QFramework系统设计&#xff1a;通用背包系统》第四章时&#xff0c;笔者使用了Odin插件&#xff0c;对Item和ItemDatabase的SO文件进行了一些优化&#xff0c;使物品页面更加紧凑、更易拓展。 核心逻辑和功能没有改动&#xff0c;整体代码量减少…

并发容器(Map、List、Set)实战及其原理

目录 JUC包下的并发容器 CopyOnWriteArrayList 应用场景 CopyOnWriteArrayList使用 CopyOnWriteArrayList原理 CopyOnWriteArrayList 的缺陷 扩展知识&#xff1a;迭代器的 fail-fast 与 fail-safe 机制 ConcurrentHashMap 应用场景 ConcurrentHashMap使用 数…

IT行业针对大数据的安全文件传输的重要性

在数字化浪潮的推动下&#xff0c;数据已成为现代社会的宝贵资源。特别是大数据&#xff0c;以其海量、多样化、高速增长和低价值密度的特性&#xff0c;对信息技术&#xff08;IT&#xff09;行业产生了深远影响。大数据的应用不仅推动了云计算、物联网和人工智能等领域的发展…

算法——前缀和算法

1. 什么是前缀和算法 前缀和算法&#xff08;Prefix Sum&#xff09;是一种用于快速计算数组元素之和的技术。它通过预先计算数组中每个位置前所有元素的累加和&#xff0c;将这些部分和存储在一个新的数组中&#xff0c;从而在需要计算某个区间的和时&#xff0c;可以通过简单…

Backtrader 文档学习- Sizers

Backtrader 文档学习- Sizers 1.概述 智能仓位 Strategy提供了交易方法&#xff0c;即&#xff1a;buy&#xff0c;sell和close。看一下buy的定义&#xff1a; def buy(self, dataNone,sizeNone, priceNone, plimitNone,exectypeNone, validNone, tradeid0, **kwargs):注意&…

SpringBoot整合Knife4j接口文档生成工具

一个好的项目&#xff0c;接口文档是非常重要的&#xff0c;除了能帮助前端和后端开发人员更快地协作完成开发任务&#xff0c;接口文档还能用来生成资源权限&#xff0c;对权限访问控制的实现有很大的帮助。 这篇文章介绍一下企业中常用的接口文档工具Knife4j&#xff08;基于…

每日一题——LeetCode1389.按既定顺序创建目标数组

方法一 splice 使用splice函数就可以在数组的指定索引位置添加元素 var createTargetArray function(nums, index) {let res[]for(let i0;i<nums.length;i){res.splice(index[i],0,nums[i])}return res }; 消耗时间和内存情况&#xff1a; 方法二 模拟 如果res[index[…

阅读《极客时间 | Kafka核心技术与实战》(一)【Kafka入门】

阅读《极客时间 | Kafka核心技术与实战》 为什么要学习Kafka消息引擎系统ABC一篇文章带你快速搞定Kafka术语我应该选择哪种Kafka&#xff1f;聊聊Kafka的版本号 为什么要学习Kafka 如果你是一名软件开发工程师的话&#xff0c;掌握 Kafka 的第一步就是要根据你掌握的编程语言去…