算法系列--动态规划--子序列(1)

💕"深思熟虑的结果往往就是说不清楚。"💕
作者:Mylvzi
文章主要内容:算法系列–动态规划–子序列(2)
在这里插入图片描述

今天带来的是算法系列--动态规划--子序列(1),是子序列问题的开篇!带大家初识子序列问题

一.什么是子序列问题

我们之前已经学习过子数组问题,子数组问题最大的特点就是求一段连续区间的xxxx,子数组问题的经典的状态表示就是以i位置为结束,xxxx,推导状态转移方程的一个经验是根据数组的结构来区分不同的结构

子序列问题本质上是对子数组问题的一个拓展,或者说子序列问题包含了子数组问题

在这里插入图片描述
子序列问题相较于子数组问题最大的不同在于序列可以是不连续的,也就是序列既可以连续又可以不连续,所以说子序列问题包含了子数组问题

但是子序列问题的状态表示和状态转移方程的推导方式和子数组问题十分相似!可以总结为以下步骤:

  1. 根据经验,确定状态表示dp[i],这一步和子数组问题中的状态表示很像,往往都是根据题目的意思+经验确定状态表示
  2. 状态转移方程:子序列/子数组问题的状态转移方程的推导方式比较固定,既按照构成子序列/子数组的形式确定,一般就分为两类
  • 单独一个 nums[i]
  • 前面一堆 + nums[i]
  1. 初始化:对于子序列问题来说,一般单独的一个数字也可以表示一种状态(1),1就表示最次状态,所以往往将dp表初始化为1
  2. 填表顺序:不固定,但一般都是从左往右的线性表
  3. 返回值:具体问题具体分析

下面是一些经典问题:

二.子序列题目讲解

1.最⻓递增⼦序列

链接: 最⻓递增⼦序列

分析:

  1. 状态表示:dp[i]表示以i为结束位置,最长的递增子序列的长度
  2. 状态转移方程:观察构成dp[i]的形式有两种,nums[i]单独一个,此时dp[i]==1,第二种形式是和之前的任意一段子序列重新构成一个新的递增子序列,设前面子序列的结束位置为j,则dp[i] =Max(dp[j] + 1)
  3. 初始化:由于一个nums[i]也可以组成一个子序列,所以最次的状态就是1,进而可以将dp表初始化为1(这样也就可以不讨论状态转移方程中nums[i]单独一个的情况)
  4. 填表顺序:从左至右
  5. 返回值:返回dp[i]的最大值

在这里插入图片描述

代码:

class Solution {public int lengthOfLIS(int[] nums) {int n = nums.length;int[] dp = new int[n];for(int i = 0 ; i < n; i++) dp[i] = 1;// 初始化dp表int ret = dp[0];// 记录最值// 填表for(int i = 1; i < n; i++) {for(int j = 0; j < i; j++) {if(nums[i] > nums[j])// 注意不是任何一个子序列都可以和nums[i]构成严格递增的子序列的  必须符合条件dp[i] = Math.max(dp[j] + 1,dp[i]);}ret = ret > dp[i] ? ret : dp[i];}return ret;}
}

注意:

子序列问题相较于子数组问题的解题过程多了一层for循环,正是因为子序列问题的不连续的特性,所以要遍历i位置之前的所有子序列,时间复杂度相较于子数组问题也更高

2.摆动序列

链接:摆动序列

分析:

  1. 状态表示:根据经验很容易想到这道题的状态表示是以i位置为结束的摆动序列的最长子序列的的长度,但是在分析接下来的状态转移方程时,发现dp[i]的状态是收到前一个数的差值的正负所影响的,如果前面的差值是负,则nums[i]与前一序列结尾的数字nums[j]的差值必须为正,反之依然,所以仅仅通过一个状态转移方程是不够的,我们需要创建出两个状态转移方程表

    f[i]:以i位置为结尾,nums[i]与nums[j]的差值为的最长子序列的长度
    g[i]:以i位置为结尾,nums[i]与nums[j]的差值为的最长子序列的长度

2.状态转移方程:
在这里插入图片描述

  1. 初始化:最次状态为1,所以两个表都初始化为1
  2. 填表:从左往右
  3. 返回值:两个表的最大值

代码:

class Solution {public int wiggleMaxLength(int[] nums) {int n = nums.length;int[] f = new int[n];// 差值为正数的dp表int[] g = new int[n];// 差值为负数的dp表for(int i = 0; i < n; i++) f[i] = g[i] = 1;int ret = f[0];for(int i = 1; i < n; i++) {for(int j = 0; j < i; j++) {if(nums[i] - nums[j] > 0) f[i] = Math.max(g[j] + 1,f[i]);else if(nums[i] - nums[j] < 0) g[i] = Math.max(f[j] + 1,g[i]);}ret = Math.max(f[i],g[i]);// 更新最大值}return ret;}
}

3.最长定差子序列

链接:最长定差子序列

分析:

  • 笔者一拿到本题就想到了最长递增子序列那道题目,只不过这里的增加的条件变为了nums[i] - nums[j] == difference,但是大致的过程是相同的,笔者兴冲冲的C,V结果发现超时(小丑了)
  • 优化策略:超时代码中时间复杂度最高的地方在于需要对j从0位置一直遍历到i - 1,再加上外层循环,时间复杂度达到O(N^2),所以需要优化这里的寻找过程
  • 这个寻找过程的目的是找到最长的定差子序列,但是实际上没有必要从0开始找,如果有两个重复的nums[j],我们只需要取其中对应的dp[j]较大的值即可.而且,我们在遍历nums[i]的过程中,可以将每次遍历得到的nums[i]和dp[i]存入到哈希表之中,这样再回头去找最长的子序列时,只需寻找key = a - difference即可,这样搜索的时间复杂度就降低到O(1)

超时代码

class Solution {public int longestSubsequence(int[] nums, int difference) {int n = nums.length;int[] dp = new int[n];for(int i = 0 ; i < n; i++) dp[i] = 1;// 初始化dp表int ret = dp[0];// 记录最值// 填表for(int i = 1; i < n; i++) {for(int j = 0; j < i; j++) {if(nums[i] - nums[j] == difference)dp[i] = dp[i] > dp[j] + 1 ? dp[i] : dp[j] + 1;}ret = ret > dp[i] ? ret : dp[i];}return ret;}
}

优化:

class Solution {public int longestSubsequence(int[] arr, int difference) {Map<Integer,Integer> hash = new HashMap<>();int ret = 1;// 记录最值for(int a : arr) {hash.put(a,hash.getOrDefault(a-difference, 0 ) + 1);// 将当前位置插入到哈希表中ret = Math.max(ret,hash.get(a));// 更新最值}return ret;}
}

4.最长数对链

链接:最长数对链

分析:

  • 本题其实和最长递增子序列很像,只不过这里换成了数对,需要注意的是,本题的子序列是可以任意挑选的,所以需要对数组进行排序

代码:

class Solution {public int findLongestChain(int[][] nums) {// 预处理数组 --转化为子序列问题Arrays.sort(nums,(a,b) -> {return a[0] - b[0];});// 此处使用的lambda表达式// 下面的思路和"最长递增子序列长度"相同int n = nums.length;int[] dp = new int[n];for(int i = 0; i < n; i++) {dp[i] = 1;}int ret = dp[0];for(int i = 1; i < n; i++) {for(int j = 0; j < i; j++) {if(nums[i][0] > nums[j][1]) {dp[i] = Math.max(dp[i],dp[j] + 1);}}ret = ret > dp[i] ? ret : dp[i];}return ret;}
}

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

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

相关文章

6.volatile与JMM

文章目录 被 volatile 修饰的变量有两大特点volatile 的内存语义volatile 凭什么可以保证有序性和可见性? 内存屏障(面试重点)解读 volatile 的两大特性内存屏障 volatile 特性两大类读屏障(Load Barrier)写屏障(Store Barrier) 四小类C源码分析四类屏障特点 如何保证有序性?…

huggingface的transformers训练gpt

目录 1.原理 2.安装 3.运行 ​编辑 4.数据集 ​编辑 4.代码 4.1 model init​编辑 forward&#xff1a; 总结&#xff1a; 关于loss和因果语言模型&#xff1a; ​编辑 交叉熵&#xff1a;​编辑 记录一下transformers库训练gpt的过程。 transformers/examples/…

klipper源码分析之simulavr测试

分析Klipper源码&#xff0c;有时需要结合下位机一起分析&#xff0c;这样才能更加全面的了解Klipper的工作原理。如果手头上有打印机主板&#xff0c;电脑当做上位机运行Klipper&#xff0c;这样是比较方便。如果手头上没有打印机主板&#xff0c;可以用simulavr模拟AVR下位机…

Linux常见指令解析一

Linux常见指令解析一 常见指令1. ls 指令2.pwd 命令3.cd 命令4.touch 命令5.mkdir 命令6.rmdir指令 && rm 指令7.man 指令8.cp 指令9.cat 命令 && tac 命令10.mv 指令11.more 指令12.less 指令13.head 指令14.tail 指令15.cal 指令 常见指令 1. ls 指令 语法…

Vscode创建php项目

1.安装中文插件&#xff08;可安装可不安装&#xff09; 2.安装主题&#xff08;可安装可不安装&#xff09; 3.安装和php相关的插件 4.打开文件夹 5.路由操作 查看项目中的route路由 浏览器中访问think 隐藏index.php入口文件 访问ThinkPHP5.1开发手册&#xff0c;复制apa…

【Python实战】——神经网络识别手写数字

&#x1f349;CSDN小墨&晓末:https://blog.csdn.net/jd1813346972 个人介绍: 研一&#xff5c;统计学&#xff5c;干货分享          擅长Python、Matlab、R等主流编程软件          累计十余项国家级比赛奖项&#xff0c;参与研究经费10w、40w级横向 文…

鸿蒙APP应用开发教程—超详细的项目结构说明

1. 新建项目 打开DevEco Studio, 选择 Create Project: 1.1 选择模版 Create Project - Choose Template 1.2 配置项目 Create Project - Configure Project 如果使用的是 DevEco 3.X 版本, 可以根据 Compile SDK版本选择不同的模式, 比如: 3.0.0(API 8)及更早 - 仅支持 …

数字图像处理学习笔记(五)

数字图像处理学习笔记&#xff08;五&#xff09; 表示与描述表示链码最小周长多边形的多边形近似&#xff08;MPP&#xff09;标记边界片段骨骼&#xff08;表示平面区域结构形状&#xff09; SIFT原理(尺度不变特征变换匹配算法:Scale-invariant feature transform)SIFT算法分…

有名的爬虫框架 colly 的特性及2个详细采集案例

一. Colly概述 前言&#xff1a;colly 是 Go 实现的比较有名的一款爬虫框架&#xff0c;而且 Go 在高并发和分布式场景的优势也正是爬虫技术所需要的。它的主要特点是轻量、快速&#xff0c;设计非常优雅&#xff0c;并且分布式的支持也非常简单&#xff0c;易于扩展。 框架简…

Linux之时间子系统(四): tick 层模块(periodic 和dynamic )

一、时间子系统的软件架构 二、tick 层模块的文件 tick-common.c tick-oneshot.c tick-sched.c tick-broadcast.c tick-broadcast-hrtimer.c 这三个文件属于tick device layer。 tick-common.c文件是periodic tick模块&#xff0c;用于管理周期性tick事件。 tick-oneshot.c文…

ubuntu22.04物理机双系统手动分区

ubuntu22.04物理机双系统手动分区 文章目录 ubuntu22.04物理机双系统手动分区1. EFI系统分区2. 交换分区3. /根分区4. /home分区分区后的信息 手动分区顺序&#xff1a;EFI系统分区(/boot/efi)、交换分区(/swap)、/根分区、/home分区。 具体参数设置&#xff1a; 1. EFI系统分…

OpenHarmony使用智能指针管理动态分配内存对象

概述 智能指针是行为类似指针的类&#xff0c;在模拟指针功能的同时提供增强特性&#xff0c;如针对具有动态分配内存对象的自动内存管理等。 自动内存管理主要是指对超出生命周期的对象正确并自动地释放其内存空间&#xff0c;以避免出现内存泄漏等相关内存问题。智能指针对…

Vue复习

1. MVVM 模型 ● Model&#xff08;模型&#xff09;&#xff1a;表示应用程序中的数据模型。它代表着应用程序中的业务逻辑和状态。 ● View&#xff08;视图&#xff09;&#xff1a;表示应用程序的用户界面。它是用户与应用程序交互的方式。 ● ViewModel&#xff08;视图模…

Docker 安装 Nginx 容器,反向代理

Docker官方镜像https://hub.docker.com/ 寻找Nginx镜像 下载Nginx镜像 docker pull nginx #下载最新版Nginx镜像 (其实此命令就等同于 : docker pull nginx:latest ) docker pull nginx:xxx #下载指定版本的Nginx镜像 (xxx指具体版本号)检查当前所有Docker下载的镜像 docker…

关于使用TCP-S7协议读写西门子PLC字符串的问题

我们可以使用TCP-S7协议读写西门子PLC&#xff0c; 比如PLC中定义一个String[50] 的地址DB300.20 地址DB300.20 DB块编号为300&#xff0c;偏移量【地址】是30 S7协议是西门子PLC自定义的协议&#xff0c;默认端口102&#xff0c;本质仍然是TCP协议的一种具体实现&#xff…

HMI界面之:医疗设备界面

一、什么是医疗HMI界面 医疗HMI界面是指医疗设备或系统中的人机界面&#xff08;Human-Machine Interface&#xff09;&#xff0c;用于与医疗设备进行交互和操作的界面。它是医疗设备中的重要组成部分&#xff0c;通过图形化、直观化的界面&#xff0c;使医护人员能够方便地控…

短剧APP系统开发:探索短剧的发展机遇,提高收益

近年来&#xff0c;短剧在各大社交平台上快速发展&#xff0c;市场规模大幅度上升&#xff0c;成为了大众闲暇时光的娱乐的首选方式之一&#xff0c;深受大众的喜爱。 与传统的影视相比&#xff0c;短剧时间短、节奏快、剧情爽&#xff0c;让给观众更加容易“上头”。对于创业…

举4例说明Python如何使用正则表达式分割字符串

在Python中&#xff0c;你可以使用re模块的split()函数来根据正则表达式分割字符串。这个函数的工作原理类似于Python内置的str.split()方法&#xff0c;但它允许你使用正则表达式作为分隔符。 示例 1: 使用单个字符作为分隔符 假设你有一个由逗号分隔的字符串&#xff0c;你可…

力扣:205. 同构字符串

前言&#xff1a;剑指offer刷题系列 问题&#xff1a; 给定两个字符串 s 和 t &#xff0c;判断它们是否是同构的。 如果 s 中的字符可以按某种映射关系替换得到 t &#xff0c;那么这两个字符串是同构的。 每个出现的字符都应当映射到另一个字符&#xff0c;同时不改变字符…

【Lazy ORM 框架学习】

Gitee 点赞关注不迷路 项目地址 快速入门 模块所属层级描述快照版本正式版本wu-database-lazy-lambdalambda针对不同数据源wu-database-lazy-orm-coreorm 核心orm核心处理wu-database-lazy-sqlsql核心处理成处理sql解析、sql执行、sql映射wu-elasticsearch-starterESESwu-hb…