算法练习第25天|491. 非递减子序列

 491. 非递减子序列

491. 非递减子序列icon-default.png?t=N7T8https://leetcode.cn/problems/non-decreasing-subsequences/

题目描述:

给你一个整数数组 nums ,找出并返回所有该数组中不同的递增子序列,递增子序列中 至少有两个元素 。你可以按 任意顺序 返回答案。

数组中可能含有重复元素,如出现两个整数相等,也可以视作递增序列的一种特殊情况。

示例 1:

输入:nums = [4,6,7,7]
输出:[[4,6],[4,6,7],[4,6,7,7],[4,7],[4,7,7],[6,7],[6,7,7],[7,7]]

示例 2:

输入:nums = [4,4,3,2,1]
输出:[[4,4]]
  • -100 <= nums[i] <= 100

思路分析:

注意,本题不能像算法练习第24天|78.子集、 90.子集II-CSDN博客中的90.子集II那样对元素组进行排序已达到元素子序列去重的目的,可以看上面的示例2,如果我们按照90题那样的做法对原数组进行排列的话【1,2,3,4,4】,就会得出不止一个非递减子序列,这显然与题目输出的【4,4】不符合。所以我们不能对原序列进行排序。

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

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

按照正常的前后顺序进行搜索,会发现两种情况下元素是不能记录的:

(1)如果当前元素比刚刚记录的元素小,那么当前元素就不能往path中添加,因为此时不符合非递减的性质。

(2)同一父节点下的那一层遍历,如果元素之前用过,那么也不能向path中添加。

上面两种情况任意一种发生,path就不能记录当前元素。所以这两种情况对应代码的逻辑关系是或||

下面开始日常的回溯三部曲:

第一步:确认回溯函数的参数与返回值。由于需要在一个集合里面取序列,所以要用到startIndex.

 vector<int> path;vector<vector<int>> result;void backTracking(vector<int> & nums, int startIndex){}

第二步:确认回溯终止条件。当startIndex达到nums.size()之后就遍历完了,return。

    vector<int> path;vector<vector<int>> result;void backTracking(vector<int> & nums, int startIndex){  if(startIndex == nums.size()){return;}

第三步:确认单层遍历逻辑。此时就要考虑到我们当前的元素nums[i]是否是上面所述的两种不能记录的情况了。条件(1)如果当前元素比刚刚记录的元素小,用(!path.empty() && nums[i] < path.back())表示;条件(2)同一父节点下该元素(数值)之前用过,用used_numbers[nums[i]+100] == 1表示。

因为题目提示了nums所有元素-100 <= nums[i] <= 100,所以我们使用一个used_numbers数组来记录元素是否用过。由于数组的下标是从0开始算的,所以我们将nums[i]+100,将元素的范围【-100,100】线性拉伸到【0,200】,总共201个数。例如,当前元素为-100时,它存在数组的开始处,当元素为-99时,它存在数组的下标1处,依次类推。使用了该元素,则对应元素置1。另外也可以用set来记录用过的数据。

        int used_numbers[201] = {0};  //记录统一父节点下哪些数字是用过的for(int i = startIndex; i < nums.size(); i++){if((!path.empty() && nums[i] < path.back())|| used_numbers[nums[i]+100] == 1)continue;//不满足if条件则表示该节点可以记录,那么记录当前节点path.push_back(nums[i]);//判断path长度是否大于等于2,如果是,则reslut记录if(path.size() > 1){result.push_back(path);}//-100到100映射到0-201used_numbers[nums[i]+100] = 1;  //用过该数字,标志为置1//因为子序列最少要有两个元素,所以我们平常的result.push_back(path)就不能直接写了//result.push_back(path);backTracking(nums, i+1);path.pop_back();}

整体代码如下:

class Solution {
public:vector<int> path;vector<vector<int>> result;void backTracking(vector<int> & nums, int startIndex){if(startIndex == nums.size()){return ;}int used_numbers[201] = {0};  //记录统一父节点下哪些数字是用过的for(int i = startIndex; i < nums.size(); i++){if((!path.empty() && nums[i] < path.back())|| used_numbers[nums[i]+100] == 1)continue;//记录当前节点path.push_back(nums[i]);if(path.size() > 1){result.push_back(path);}//-100到100映射到0-201used_numbers[nums[i]+100] = 1;  //用过该数字,标志为置1//因为子序列最少要有两个元素,所以我们平常的result.push_back(path)就不能直接写了//result.push_back(path);backTracking(nums, i+1);path.pop_back();}}vector<vector<int>> findSubsequences(vector<int>& nums) {backTracking(nums, 0);return result;}
};

下面是使用unordered_set<int>来记录重复元素的写法:

class Solution {
public:vector<int> path;vector<vector<int>> result;void backTracking(vector<int> & nums, int startIndex){if(startIndex == nums.size()){return ;}unordered_set<int> used_numbers;  //记录统一父节点下哪些数字是用过的for(int i = startIndex; i < nums.size(); i++){if((!path.empty() && nums[i] < path.back())|| used_numbers.find(nums[i]) != used_numbers.end())continue;//记录当前节点path.push_back(nums[i]);if(path.size() > 1){result.push_back(path);}//-100到100映射到0-201used_numbers.insert(nums[i]);  //用过该数字,标志为置1//因为子序列最少要有两个元素,所以我们平常的result.push_back(path)就不能直接写了//result.push_back(path);backTracking(nums, i+1);path.pop_back();}}vector<vector<int>> findSubsequences(vector<int>& nums) {backTracking(nums, 0);return result;}
};

注意:不管是使用数组还是set来存放使用过的数字,它们都只存在与当前递归层,即下一层的递归中数组和set都会重新创建并初始化,然后for循环在同一层中遍历,这就保证了同一父节点下可以查找元素使用已经用过。

另外,在使用set时,程序运行的时候对unordered_set 频繁的insert,unordered_set需要做哈希映射(也就是把key通过hash function映射为唯一的哈希值)相对费时间,而且每次重新定义set,insert的时候其底层的符号表也要做相应的扩充,也是费事的。使用数组程序还快一些。算法训练第5天|哈希表理论基础 242.有效的字母异位词 349. 两个数组的交集 202. 快乐数 1. 两数之和-CSDN博客

在上面这篇博文349题中,提到了数组和set作为哈西表时各自的应用场景:

而且如果哈希值比较少、特别分散、跨度非常大,使用数组就造成空间的极大浪费,优先使用set和map。数组,set,map都可以做哈希表,而且数组干的活,map和set都能干,但如果数值范围小的话能用数组尽量用数组

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

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

相关文章

Linux、Windows安装python环境(最新版及历史版本指定版本)-python

目录 一、Linux环境二、windows环境最新版本下载指定版本下载 python 官网地址&#xff1a; https://www.python.org/ 一、Linux环境 以openEuler/CentOS为例 查看可安装python源版本 dnf provides python*默认安装新版本 dnf install -y python3. 进入python python退出p…

电源小白入门学习8——电荷泵电路原理及使用注意事项

电源小白入门学习8——电荷泵电路原理及使用注意事项 电荷泵简介电荷泵原理电荷泵设计过程中需要注意的点fly电容的安秒平衡DC/DC功率转换技术对比 电荷泵简介 电荷泵&#xff08;Charge Pump&#xff09;是一种电路拓扑结构&#xff0c;用于实现电压升压或降压的功能。它通过…

sh发送邮件如何通过配置SMTP服务器来实现?

sh发送邮件的操作方法&#xff1f;如何使用Shell脚本自动发信&#xff1f; 在Shell脚本中实现邮件发送功能是一项常见需求&#xff0c;特别是在自动化任务执行或系统监控中。AokSend将介绍如何通过配置SMTP服务器来实现sh发送邮件的方法和注意事项。 sh发送邮件&#xff1a;安…

【已解决】Error in the HTTP2 framing layer

1.问题描述 在使用git将代码上传github的时候在最后一部push的时候遇到这个fatal 2.解决方案 由于我原先设置的origin是http协议下的&#xff0c;如下 git remote add origin https://github.com/Charlesbibi/Simple_Cloud.githttp协议下行不通不妨试一试ssh协议下&#xff…

跟风报考PMP,我真的后悔了

真的太香吧&#xff01; 我一开始没打算报考PMP证书的&#xff0c;但是我看身边很多朋友都因为PMP证书得到了升职加薪&#xff0c;这让我实在是一整个羡慕住了&#xff0c;所以我也去报考了PMP。 报考PMP前期我做了什么&#xff1f; 由于我是零基础&#xff0c;没有什么项目…

探索网格生成技术在AI去衣应用中的作用

引言&#xff1a; 随着人工智能技术的飞速发展&#xff0c;其在图像处理和计算机视觉领域的应用日益广泛。其中&#xff0c;AI去衣技术作为一种新兴的应用&#xff0c;引起了广泛的关注和讨论。然而&#xff0c;要实现这一功能并非易事&#xff0c;需要借助于先进的算法和技术。…

Mybatis第一讲——你会Mybatis吗?

文章目录 什么是MybatisMybatis的作用是什么 Mybatis 怎么使用注解的方式注解的多种使用Options注解ResultType注解 XML的方式update标签 #{} 和 ${}符号的区别#{}占位${}占位 ${}占位的危险性(SQL注入)数据库连接池 什么是Mybatis 首先什么是Mybatis呢&#xff1f;Mybatis是一…

latex bib引参考文献

1.bib内容 2.sn-mathphys-num是官方的参考文献格式 3.不用导cite包&#xff0c;文中这么写 4.end document前ckwx是自己命名的bib的名字

Ollama教程,本地部署大模型Ollama,docker安装方法,仅供学习使用

不可商用&#xff01;&#xff01;仅仅提供学习使用&#xff01; 先上视频教学&#xff1a; Ollama教程&#xff0c;本地部署大模型Ollama&#xff0c;docker安装方法&#xff0c;仅供学习使用&#xff01; 资料获取 &#xff1a; Ollama下载包和安装文档在这里&#xff1…

Web自动化测试-掌握selenium工具用法,使用WebDriver测试Chrome/FireFox网页(Java

目录 一、在Eclipse中构建Maven项目 1.全局配置Maven 2.配置JDK路径 3.创建Maven项目 4.引入selenium-java依赖 二、Chrome自动化脚本编写 1.创建一个ChromeTest类 2.测试ChromeDriver 3.下载chromedriver驱动 4.在脚本中通过System.setProperty方法指定chromedriver的…

Ubuntu 20.04安装CMake 3.22.6版本

Ubuntu 20.04通过apt安装的cmake版本是3.16.3&#xff0c;默认安装到/usr/bin/cmake路径。 $ cmake Command cmake not found, but can be installed with:sudo snap install cmake # version 3.29.3, or sudo apt install cmake # version 3.16.3-1ubuntu1.20.04.1See sna…

Unity + 雷达 粒子互动(待更新)

效果预览: 花海(带移动方向) VFX 实例 脚本示例 使用TouchScript,计算玩家是否移动,且计算移动方向 using System.Collections; using System.Collections.Generic; using TouchScript; using TouchScript.Pointers; using UnityEngine; using UnityEngine.VFX;public …

AI预测福彩3D采取888=3策略+和值012路一缩定乾坤测试6月1日预测第8弹

今天继续基于8883的大底&#xff0c;使用尽可能少的条件进行缩号。好了&#xff0c;直接上结果吧~ 首先&#xff0c;888定位如下&#xff1a; 百位&#xff1a;6,5,4,7,8,9,1,0 十位&#xff1a;7,8,6,5,9,3,1,0 个位&#xff1a;5,7,6,4,2,…

linux 内核哪种锁可以递归调用 ?

当数据被多线程并发访问(读/写)时&#xff0c;需要对数据加锁。linux 内核中常用的锁有两类&#xff1a;自旋锁和互斥体。在使用锁的时候&#xff0c;最常见的 bug 是死锁问题&#xff0c;死锁问题很多时候比较难定位&#xff0c;并且影响较大。本文先会介绍两种引起死锁的原因…

Java-----String类

1.String类的重要性 经过了C语言的学习&#xff0c;我们认识了字符串&#xff0c;但在C语言中&#xff0c;我们表示字符串进行操作的话需要通过字符指针或者字符数组&#xff0c;可以使用标准库中提供的一系列方法对字符串的内容进行操作&#xff0c;但这种表达和操作数据的方…

沟通程序化(1):跟着鬼谷子学沟通—“飞箝”之术

沟通的基础需要倾听&#xff0c;但如果对方听不进你的话&#xff0c;即便你说的再有道理&#xff0c;对方也很难入心。让我们看看鬼谷子的“飞箝”之术能给我们带来什么样的启发吧&#xff01; “飞箝”之术&#xff0c;源自中国古代兵法家、纵横家鼻祖鬼谷子的智慧&#xff0…

SpringBootWeb 篇-深入了解 Spring 异常处理、事务管理和配置文件参数配置化、yml 配置文件

&#x1f525;博客主页&#xff1a; 【小扳_-CSDN博客】 ❤感谢大家点赞&#x1f44d;收藏⭐评论✍ 文章目录 1.0 配置文件 1.1 yml 配置文件 1.2 参数配置化 1.2.1 使用 Value 注解注入单个配置参数 1.2.2 使用 ConfigurationProperties 注解将一组相关配置参数注入到一个类中…

discuz论坛怎么修改备案信息

大家好&#xff0c;今天给大家分享下discuz如何填写备案信息并且展示在网站首页。大家都知道国内网站都需要备案&#xff0c;不通过备案的网站上是没办法通过域名打开的。大家也可以通过搜索网创有方&#xff0c;或者直接点击网创有方 查看悬挂备案号后的效果。 首先大家可以看…

安全测试扫描利器-Burpsuite

&#x1f525; 交流讨论&#xff1a;欢迎加入我们一起学习&#xff01; &#x1f525; 资源分享&#xff1a;耗时200小时精选的「软件测试」资料包 &#x1f525; 教程推荐&#xff1a;火遍全网的《软件测试》教程 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1…

vscode常用插件及插件安装方式

一、常用插件 Chinese (Simplified) (简体中文) Language Pack for Visual Studio Code 说明&#xff1a;中文语言包扩展&#xff08;简体&#xff09; open in browser 说明&#xff1a;可以在默认浏览器或应用程序中打开当前文件 Auto Rename Tag 说明&#xff1a;自动重…