【算法练习Day24】递增子序列全排列全排列 II

在这里插入图片描述

​📝个人主页:@Sherry的成长之路
🏠学习社区:Sherry的成长之路(个人社区)
📖专栏链接:练题
🎯长路漫漫浩浩,万事皆有期待

文章目录

  • 递增子序列
    • 容易出错的地方
  • 全排列
  • 全排列 II
  • 总结:

递增子序列

491. 递增子序列 - 力扣(LeetCode)
在这里插入图片描述

递增子序列也是一道求子集的问题,但是这道题与子集II的不同之处是,这道题不能够对数组排序,而且要求求出答案序列为2以上的递增子序列。

不能排序求递增子序列是有点难度的,需要一些技巧,首先给定数组nums可能含有重复数据,而做过往期的朋友都知道,去重是一定要排序的,那么不排序我们怎么做呢?

思路是这样的,在函数实现的内部,我们定义一个哈希表,该哈希表存储的是当前数层中我们取过那些数据,取过的我们标记一下,这里用数组还是其他什么做哈希都可以,但是数组更高效。

代码如下:

class Solution {
public:vector<vector<int>> result;vector<int> path;void backtracking(vector<int>&nums,int startIndex){if(path.size()>1){result.push_back(path);}unordered_set<int> uset;for(int i=startIndex;i<nums.size();i++){if((!path.empty()&&nums[i]<path.back())||uset.find(nums[i])!=uset.end()){continue;}uset.insert(nums[i]);path.push_back(nums[i]);backtracking(nums,i+1);path.pop_back();}}vector<vector<int>> findSubsequences(vector<int>& nums) {result.clear();path.clear();backtracking(nums,0);return result;}
};

没有接触过不能排序而去重的逻辑,有时候还真的想不出来这种解法,因为它是在递归函数内部中间创建的哈希表,我们并不需要对它进行回溯,原因是每进入下一层递归,也就是增加了递归深度它会新建一个哈希,来保存当前树层所插入过的节点,换句话说它保存的是树的横向结构。我们在for循环中,通过判断该层的哈希里要插入的数据有没有插进去过,即可完成数层去重。而控制递增子序列就显得格外简单了,我们仅仅是比较一下当前我们要插入的合法数据和要插入的数组的最后一个数据谁大就可以了,如果要插入的大直接插入,不行的话还是continue,理由还是相同的,continue是为了能让i往后走,找之后的数据看看能不能接着插入进数组。

容易出错的地方

由于我们是根据题目给定数组nums对应的个数据做的哈希,为了判断这个数是否在该树层用过。当我们用数组做哈希结构,要注意测试用例的数据范围,数据包含负数,且是-100-100的数据,所以我们用201个空间并且使数据主动加上100,来避免对负数下标的访问,这是有讲究的定义数组,而非随便取数组大小。当然选用set或者map可以避免这方面的判断,但是当数据范围不大时候,选用数组做哈希可以提高运行效率。

全排列

46. 全排列 - 力扣(LeetCode)
在这里插入图片描述

终于进到了回溯算法的下一个主题,全排列,到了排列部分也就意味着我们的回溯算法例题,快要进入尾声了。排列和组合的模板不同。换句话说,组合问题,子集问题,切割问题三种问题实际上都可以类比为组合问题的大类里面,因为无论是子集问题还是切割问题,都是不能向前去取数

但是排列可以,{1,2}和{2,1}是完全不同的排列,这就意味着我们在写递归时要考虑到这一点,如何即清楚我们下一层从哪里开始,又要可以遍历到这个数之前的数据呢?答案是用到一个数组来保存,这个数组标识着我们哪些数已经取过了,哪些数没有取,也就是说排列问题(往回取数的问题)无论是否涉及到去重都要加入一个标记数组。

先看代码分析

class Solution {
public:vector<vector<int>> result;vector<int> path;void backtracking(vector<int>& nums,vector<bool>&used){if(path.size()==nums.size()){result.push_back(path);return;}for(int i=0;i<nums.size();i++){if(used[i]==true){continue;}used[i]=true;path.push_back(nums[i]);backtracking(nums,used);path.pop_back();used[i]=false;}}vector<vector<int>> permute(vector<int>& nums) {result.clear();path.clear();vector<bool> used(nums.size(),false);backtracking(nums,used);return result;}
};

用标记数组是如何实现的呢?在取数之后做标记,往深层递归时,由于被取过的数仍然被标记着,我们可以让i=0每次让它从第一个数开始找,碰到没有被标记的我们直接加入path中,那么是如何像示例中一样,由{1,2,3}逐渐转变为{2,1,3}的呢?实际上也是由于回溯,回溯删除1之后,此时i=0上去i++变为1,这个时候取到的是nums[1]也就是2了,递归问题的取数有时候会突然想不明白,可以自己用编译器调试一下看看,递归是如何进行的,回溯又是如何回溯的,多调试才有利于对递归更深的了解。

全排列 II

47. 全排列 II - 力扣(LeetCode)
在这里插入图片描述

知道了全排列的做法,那么全排列II也就变得简单一些了,一样的套路加上对数据的树层(横向上)去重就可以达到题目要求的效果了。这里可能会想,我们这道题的去重也要重新定义一个新的数组来保存吗?

肯定是不需要的,我们的标记数组也就是去重数组,因为它们都是起到标记一个数是否被取到,所以两个思路用一个数组是完全可以的。依旧是将给定数组先进行排序,使相同的数据挨在一起,这样才有利于排序,去重时完全按照以往去重的规则。

class Solution {
private:vector<vector<int>> result;vector<int> path;void backtracking (vector<int>& nums, vector<bool>& used){// 此时说明找到了一组if (path.size() == nums.size()) {result.push_back(path);return;}for (int i = 0; i < nums.size(); i++) {// used[i - 1] == true,说明同一树枝nums[i - 1]使用过// used[i - 1] == false,说明同一树层nums[i - 1]使用过// 如果同一树层nums[i - 1]使用过则直接跳过if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) {continue;}if (used[i] == false) {used[i] = true;path.push_back(nums[i]);backtracking(nums, used);path.pop_back();used[i] = false;}}}
public:vector<vector<int>> permuteUnique(vector<int>& nums) {result.clear();path.clear();sort(nums.begin(), nums.end()); // 排序vector<bool> used(nums.size(), false);backtracking(nums, used);return result;}
};

看到这个代码可能有同学会产生疑惑,我们这里还用used[i]来判断,那出现数组相同数据时候例如{1,1,2}会不会第二个1进不去啊,这里大可不必担心,因为我们判断的是used[i]也就是标记数组的位置,是第一个位置还是第二个位置是否为1的判断,与你本身数值是什么没有任何关系,这一点一定要注意。

总结:

今天我们完成了递增子序列、全排列、全排列 II三道题,相关的思想需要多复习回顾。接下来,我们继续进行算法练习。希望我的文章和讲解能对大家的学习提供一些帮助。

当然,本文仍有许多不足之处,欢迎各位小伙伴们随时私信交流、批评指正!我们下期见~

在这里插入图片描述

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

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

相关文章

安装Git和git命令使用

文章目录 安装Git创建版本库版本回退工作区和暂存区管理修改撤销修改 安装Git 在Windows上安装Git 在Windows上使用Git&#xff0c;可以从Git官网直接下载安装程序&#xff0c;然后按默认选项安装即可。 安装完成后&#xff0c;在开始菜单里找到“Git”->“Git Bash”&…

CentOS 7 安装 nginx

CentOS 7 安装 nginx 官网下载 nginx nginx官网下载链接&#xff1a;http://nginx.org/en/download.html 下载稳定版即可 将压缩包上传到 CentOS 7 虚拟机中 这里由于上传工具很多&#xff0c;不予展示 安装步骤 检查是否存在 nginx&#xff08;有的话需要卸载掉自带的&a…

【C++ 学习 ㉙】- 详解 C++11 的 constexpr 和 decltype 关键字

目录 一、constexpr 关键字 1.1 - constexpr 修饰普通变量 1.2 - constexpr 修饰函数 1.3 - constexpr 修饰类的构造函数 1.4 - constexpr 和 const 的区别 二、decltype 关键字 2.1 - 推导规则 2.2 - 实际应用 一、constexpr 关键字 constexpr 是 C11 新引入的关键字…

蓝牙资讯|AirPods Pro 2推送新固件,苹果Find My功能受到好评

苹果公司今天面向采用 Lightning 端口和 USB-C 端口的 AirPods Pro 2 耳机&#xff0c;更新推出了内部编号为 6A305 的全新固件&#xff0c;高于 10 月 10 日发布的 6A303 更新。 苹果官方并没有公布固件的更新日志&#xff0c;目前尚不清楚具体引入了哪些新功能、新特性。苹…

2019年亚太杯APMCM数学建模大赛A题基于图像分析的二氧化硅熔化表示模型求解全过程文档及程序

2019年亚太杯APMCM数学建模大赛 A题 基于图像分析的二氧化硅熔化表示模型 原题再现 铁尾矿的主要成分是二氧化硅&#xff0c;而二氧化硅是铁尾矿成分中最难熔化的部分。因此&#xff0c;铁尾矿的熔融行为可以用二氧化硅的熔融行为来表示。然而&#xff0c;高温熔池的温度超过…

jdk21的外部函数和内存API(官方翻译)

1、jdk21&#xff1a; 引入一个 API&#xff0c;通过该 API&#xff0c;Java 程序可以与 Java 运行时之外的代码和数据进行互操作。通过有效地调用外部函数&#xff08;即JVM外部的代码&#xff09;和安全地访问外部内存&#xff08;即不由JVM管理的内存&#xff09;&#xf…

统计学习方法 感知机

文章目录 统计学习方法 感知机模型定义学习策略学习算法原始算法对偶算法 学习算法的收敛性 统计学习方法 感知机 读李航的《统计学习方法》时&#xff0c;关于感知机的笔记。 感知机&#xff08;perceptron&#xff09;是一种二元分类的线性分类模型&#xff0c;属于判别模型…

大模型,重构自动驾驶

文&#xff5c;刘俊宏 编&#xff5c;王一粟 大模型如何重构自动驾驶&#xff1f;答案已经逐渐露出水面。 “在大数据、大模型为特征&#xff0c;以数据驱动为开发模式的自动驾驶3.0时代&#xff0c;自动驾驶大模型将在车端、云端上实现一个统一的端到端的平台管理。”毫末智…

uni-app:实现时钟自走(动态时钟效果)

效果 核心代码 使用钩子函数 mounted()&#xff0c;设置定时器&#xff0c;是指每秒都要去执行时间的获取&#xff0c;以至于实现时间自走的效果 mounted() { this.updateTime(); // 初始化时间 setInterval(this.updateTime, 1000); // 每秒更新时间 }, 自定义方法…

NodeMCU ESP8266 读取按键外部输入信号详解(图文并茂)

NodeMCU ESP8266 读取按键外部输入信号教程&#xff08;图文并茂&#xff09; 文章目录 NodeMCU ESP8266 读取按键外部输入信号教程&#xff08;图文并茂&#xff09;前言按键输入常用接口pinModedigitalRead 示例代码结论 前言 ESP8266如何检测外部信号的输入&#xff0c;通常…

Java封装:面向对象的三大特性之一

&#x1f451;专栏内容&#xff1a;Java⛪个人主页&#xff1a;子夜的星的主页&#x1f495;座右铭&#xff1a;前路未远&#xff0c;步履不停 目录 一、封装的概念二、访问修饰限定符三、包1、包的概念2、导入包中的类3、常见的包 嗨&#xff01;前面我们简单的认识了一下什么…

常用的设计模式以及操作Redis、MySQL数据库、各种MQ、数据类型转换的方法

文章目录 &#x1f31f; 如何优雅地写出高质量的Java代码&#x1f34a; 设计模式&#x1f389; 单例模式&#x1f389; 工厂模式&#x1f389; 观察者模式 &#x1f34a; 操作Redis&#x1f389; 连接Redis&#x1f389; 存储数据&#x1f389; 获取数据&#x1f389; 删除数据…

Java注解处理器APT

注解处理器介绍 什么是APT&#xff1f; 在JDK6的时候引入了JSR269的标准&#xff0c;即APT&#xff08;Annotation Processing Tool&#xff09;&#xff0c;用于在编译时处理源代码中的注解&#xff0c;从而生成额外的代码、配置文件或其他资源。与传统的运行时反射相比&…

深入探索Sharding JDBC:分库分表的利器

随着互联网应用的不断发展和用户量的不断增加&#xff0c;传统的数据库在应对高并发和大数据量的场景下面临着巨大的挑战。为了解决这一问题&#xff0c;分库分表成为了一个非常流行的方案。分库分表主流的技术包括MyCat和Sharding JDBC。我们来通过一张图来了解这两者有什么区…

C语言——二周目——程序的翻译与执行环境

一、程序环境 对于一个C语言程序的实现&#xff0c;整个过程一般存在两个不同的环境&#xff0c;分别是翻译环境与执行环境。在翻译环境中&#xff0c;我们所写的源代码经过一系列处理被转换成为可执行的机器指令&#xff1b;在执行环境中&#xff0c;会实际执行代码。 整个程序…

Open3D(C++) 最小二乘拟合平面(拉格朗日乘子法)

目录 一、算法原理二、代码实现三、结果展示本文由CSDN点云侠原创,原文链接。 一、算法原理 设拟合出的平面方程为: a x + b y + c

手写redux的connect方法, 使用了subscribe获取最新数据

一. 公共方法文件 1. connect文件 import React, { useState } from "react"; import MyContext from "./MyContext"; import _ from "lodash";// 模拟react-redux的 connect高阶函数 const connect (mapStateToProps, mapDispatchToProps) &…

数学建模——最优连接(基于最小支撑树)

一、概念 1、图的生成树 由图G(V,E)的生成子图G1(V,E1)(E1是E的子集&#xff09;是一棵树&#xff0c;则称该树为图G的生成树&#xff08;支撑树&#xff09;&#xff0c;简称G的树。图G有支撑树的充分必要条件为图G连通。 2、最小生成树问题 连通图G(V,E)&#xff0c;每条边…

STM32串口

前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; 目前已经学习了GPIO的输入输出&#xff0c;但是没有完整的显示信息&#xff0c;最便宜的显示就是串口。 000 -111 AVR单片机 已经学会过了&#xff0c; 提示&#xff1a;以下是本篇文章正文内容&#x…

Servlet的生命周期

2023.10.18 WEB容器创建的Servlet对象&#xff0c;这些Servlet对象都会被放到一个集合当中&#xff08;HashMap&#xff09;&#xff0c;这个集合当中存储了Servlet对象和请求路径之间的关系 。只有放到这个HashMap集合中的Servlet才能够被WEB容器管理&#xff0c;自己new的Ser…