「动态规划」线性DP:最长上升子序列(LIS)|编辑距离 / LeetCode 300|72(C++)

概述

DP,即动态规划是解决最优化问题的一类算法,我们要通过将原始问题分解成规模更小的、相似的子问题,通过求解这些易求解的子问题来计算原始问题。

线性DP是一类基本DP,我们来通过它感受DP算法的奥义。


最长上升子序列(LIS)

LIS问题,即最长上升子序列问题,是指找到原始序列中最长的单调元素序列这些(这些元素在原始序列中不一定是挨在一起的)。

LeetCode 2521:

给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。

子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。

示例 1:

输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。

示例 2:

输入:nums = [0,1,0,3,2,3]
输出:4

思路

我们来找到规模更小的子问题。

对于本题来说,什么是规模更小的子问题?

如果在故事的最后我们希望找到整个数组的LIS,不妨先找到整个数组中更小规模的上升序列

如果我们用称可能的答案为一个状态,那么我们需要两个要素来表示这个状态,一个是描述规模的范围,一个描述这一段上升序列的长度。

那我们不妨定义 int dp[N],其中dp[i] = num,表示从原始数组中以nums[i]结尾的LIS为num。

当我们未开始计算时,dp[i] = 1,因为至少nums[i]这一个元素算是一个长度为1的递增子序列。

具体的,

const int n = nums.size();    // 获取数组的长度
vector<int> dp(n, 1);         // 考虑到n是一个运行期变量,我们使用vector

现在,我们来求解dp[i] = ? 的子问题。


算法过程

每个子问题都要被求解,前提是比它小的问题被求解了,而不论大小规模,这些问题的求解过程是一样的。

 求解DP问题,这被称为状态转移,也就是我们从小规模的状态推导出大规模的状态。

对于下标i来说, dp[i] = dp[j] + 1,其中 j < i && nums[j] < nums[i],考虑到可能有多个j符合情况,我们取最大值。

从0计算到n - 1,当计算 i 时,j 一定计算过了,所以这是合理的。

直观来说,我们需要一个二重循环:

for (int i = 0; i < n; i++) {for (int j = 0; j < i; j++)if (nums[j] < nums[i])dp[i] = max(dp[i], dp[j] + 1);}

Code

class Solution {
public:int lengthOfLIS(vector<int>& nums) {const int n = nums.size();vector<int> dp(n, 1);int ans = INT_MIN;for (int i = 0; i < n; i++) {for (int j = 0; j < i; j++)if (nums[j] < nums[i])dp[i] = max(dp[i], dp[j] + 1);ans = max(ans, dp[i]);}return ans;}
};

这种枚举是O(n²),怎么优化呢? 

优化方案

还记得二分查找吗?

「数组」二分查找模版|二段性分析|恢复二段性 / LeetCode 35|33|81(C++)

有两件事:

  • LIS一定是单调的。
  • 子问题LIS的起点越小、差分(LIS[i] - LIS[i - 1])越小,即上升越缓,越有利于后续的元素追加到末尾。

这两件事很直观,几乎无须证明。

在遍历到i时,我们不妨记录当前LIS,然后这么做:

  • 若nums[i]可以追加,则直接追加。
  • 若nums[i]不可以追加,则二分找到LIS中当前大于nums[i]的元素,如果这个元素的上一个元素小于nums[i],则用nums[i]替换这个元素,这就使得LIS的差分减小。

二分是由LIS的单调性保证的。

Code

class Solution {
public:int lengthOfLIS(vector<int>& nums) {const int n = nums.size();vector<int> LIS(n);int len = 0;for (int i = 0; i < n; i++)if (!len || nums[i] > LIS[len - 1])LIS[len++] = nums[i];else {auto pos = upper_bound(LIS.begin(), LIS.begin() + len, nums[i]);if(pos == LIS.begin() || *prev(pos) < nums[i])*pos = nums[i];}return len;}
};  

复杂度

时间复杂度: O(nlogn) //需求解n个状态,每次求解通过二分进行。

空间复杂度: O(n)         //预留dp数组空间


编辑距离

编辑距离,也称为Levenshtein Distance,是衡量两个字符串差异的一种度量方法。它定义为将一个字符串转换成另一个字符串所需的最少编辑操作次数,包括插入、删除和替换字符。编辑距离越小,两个字符串越相似。

LeetCode 72:

给你两个单词 word1 和 word2, 请返回将 word1 转换成 word2 所使用的最少操作数  。

你可以对一个单词进行如下三种操作:

  • 插入一个字符
  • 删除一个字符
  • 替换一个字符

示例 1:

输入:word1 = "horse", word2 = "ros"
输出:3
解释:
horse -> rorse (将 'h' 替换为 'r')
rorse -> rose (删除 'r')
rose -> ros (删除 'e')

示例 2:

输入:word1 = "intention", word2 = "execution"
输出:5
解释:
intention -> inention (删除 't')
inention -> enention (将 'i' 替换为 'e')
enention -> exention (将 'n' 替换为 'x')
exention -> exection (将 'n' 替换为 'c')
exection -> execution (插入 'u')

思路

依旧要将原始问题处理成规模更小的子问题。

上一题倒是很爽,要存储两个信息,于是就用下标和值处理了,但这次我们需要三个信息,就需要二维数组了。

那我们不妨定义 `int dp[N][M]`,其中dp[i][j] = num,

表示从数组1中[1,  i]子数组与数组2中[1,  j]子数组的编辑距离为num。

做如下初始化:

for (int i = 1; i <= n; i++)dp[i][0] = i;
for (int j = 1; j <= m; j++)dp[0][j] = j;

*注意*:dp[i]对应原始的word[i - 1],我们从i = 1开始循环,即做了一格偏移,这是为了处理边界,直观来讲,可以认为dp[i]代表原始数组的前i个字符。 

解题过程

我们能想到很直观的二维循环。

有两种可能:

  • word[i] == word[j],这是好事啊,编辑距离无需增加,dp[i][j] = dp[i - 1][j - 1];
  • word[i] != word[j],dp[i][j] 至少为 dp[i - 1][j - 1],然后考虑:删除/替换/插入,这三者不过都是让二者相等的手段,我们只需要考虑之前的事,然后+1就行了。
  1. 考虑删掉word1[i - 1]/在word2[j - 1]后插入word1[i - 1],则dp[i][j] = dp[i - 1][j] + 1;
  2. 考虑删掉word2[j - 1]/在word1[i - 1]后插入word2[j - 1],则dp[i][j] = dp[i][j - 1] + 1;
  3. 考虑将word1[i - 1]替换为word2[j - 1],则dp[i][j] = dp[i - 1][j - 1] + 1;

注意这里dp[i]对应word[i - 1]。

以上三者,取最大值:

for (int i = 1; i <= n; i++)for (int j = 1; j <= m; j++) {dp[i][j] = dp[i - 1][j - 1];if (word1[i - 1] != word2[j - 1])dp[i][j] = min({dp[i][j], dp[i - 1][j], dp[i][j - 1]}) + 1;}

计算 i 和 j 时,i - 1 和 j - 1一定计算过了,这是合法的。

Code

class Solution {
public:int minDistance(string word1, string word2) {const int n = word1.size(), m = word2.size();vector dp(n + 1, vector(m + 1, 0));for (int i = 1; i <= n; i++)dp[i][0] = i;for (int j = 1; j <= m; j++)dp[0][j] = j;for (int i = 1; i <= n; i++)for (int j = 1; j <= m; j++) {dp[i][j] = dp[i - 1][j - 1];if (word1[i - 1] != word2[j - 1])dp[i][j] = min({dp[i][j], dp[i - 1][j], dp[i][j - 1]}) + 1;}return dp.back().back();}
};

复杂度

时间复杂度: O(nm) //需求解n*m个状态。

空间复杂度: O(nm) //预留n*m个空间。


总结

线性dp的状态转移依靠的是非常直观的线性增长的问题规模。

dp算法都是通过求解子问题进而求解原始问题的一类算法,希望你了解线性dp这类基本dp后有所体会。

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

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

相关文章

【NumPy完全指南】从基础操作到高性能计算实战

&#x1f4d1; 目录 一、NumPy核心价值1.1 科学计算现状分析1.2 ndarray设计哲学 二、核心数据结构解析2.1 ndarray内存布局2.2 数据类型体系 三、矢量化编程实践3.1 通用函数(ufunc)示例3.2 广播机制图解 四、高性能计算进阶4.1 内存预分配策略4.2 Cython混合编程 五、典型应用…

你的项目有‘哇‘点吗?

你的项目有哇点吗&#xff1f; 刷了一下午招聘软件&#xff0c;发现没&#xff1f;大厂JD里总爱写有创新力者优先——可你们的简历&#xff0c;创新力还不如食堂菜单&#xff01; 程序员写项目最大的误区&#xff1a;把创新当彩蛋藏最后&#xff01;什么参与需求评审负责模块…

2025年危化品安全员考试题库及答案

一、单选题 126.安全生产监督管理部门和负有安全生产监督管理职责的有关部门逐级上报事故情况,每级上报的时间不得超过&#xff08;&#xff09;小时。 A.2 B.6 C.12 答案&#xff1a;A 127.按照《安全生产法》规定,危险化学品生产经营单位的从业人员不服从管理,违反安全生…

第十六届蓝桥杯 C/C++ B组 题解

做之前的真题就可以发现&#xff0c;蓝桥杯特别喜欢出找规律的题&#xff0c;但是我还是低估了官方的执念。本博客用于记录第一次蓝桥的过程&#xff0c;代码写的很烂&#xff0c;洛谷已经有的题解&#xff0c;这里不再赘述&#xff0c;只说自己遇到的问题。用于以后回顾和查找…

C++ 基于多设计模式下的同步异步⽇志系统-2项目实现

⽇志系统框架设计 1.⽇志等级模块:对输出⽇志的等级进⾏划分&#xff0c;以便于控制⽇志的输出&#xff0c;并提供等级枚举转字符串功能。 ◦ OFF&#xff1a;关闭 ◦ DEBUG&#xff1a;调试&#xff0c;调试时的关键信息输出。 ◦ INFO&#xff1a;提⽰&#xff0c;普通的提⽰…

提示词工程(GOT)把思维链推理过程图结构化

Graph of Thoughts&#xff08;GOT&#xff09;&#xff1f; 思维图&#xff08;Graph of Thoughts&#xff09;是一种结构化的表示方法&#xff0c;用于描述和组织模型的推理过程。它将信息和思维过程以图的形式表达&#xff0c;其中节点代表想法或信息&#xff0c;边代表它们…

登录github失败---解决方案

登录github失败—解决方案 1.使用 Microsoft Edge 浏览器 2.https://www.itdog.cn/dns/ 查询 github.global.ssl.fastly.net github.com 两个 域名的 IP 3.修改DNS 为 8.8.8.8 8.8.4.4 4.修改windows hosts 文件 5. 使用 Microsoft Edge 浏览器 打开github.com

Spring AOP概念及其实现

一、什么是AOP 全称Aspect Oriented Programming&#xff0c;即面向切面编程&#xff0c;AOP是Spring框架的第二大核心&#xff0c;第一大为IOC。什么是面向切面编程&#xff1f;切面就是指某一类特定的问题&#xff0c;所以AOP也可以称为面向特定方法编程。例如对异常的统一处…

强化学习_Paper_2017_Curiosity-driven Exploration by Self-supervised Prediction

paper Link: ICM: Curiosity-driven Exploration by Self-supervised Prediction GITHUB Link: 官方: noreward-rl 1- 主要贡献 对好奇心进行定义与建模 好奇心定义&#xff1a;next state的prediction error作为该state novelty 如果智能体真的“懂”一个state&#xff0c;那…

spring中的@Configuration注解详解

一、概述与核心作用 Configuration是Spring框架中用于定义配置类的核心注解&#xff0c;旨在替代传统的XML配置方式&#xff0c;通过Java代码实现Bean的声明、依赖管理及环境配置。其核心作用包括&#xff1a; 标识配置类&#xff1a;标记一个类为Spring的配置类&#xff0c;…

7.计算机网络相关术语

7. 计算机网络相关术语 ACK (Acknowledgement) 确认 ADSL (Asymmetric Digital Subscriber Line) 非对称数字用户线 AP (Access Point) 接入点 AP (Application) 应用程序 API (Application Programming Interface) 应用编程接口 APNIC (Asia Pacific Network Informatio…

Hadoop 集群基础指令指南

目录 &#x1f9e9; 一、Hadoop 基础服务管理指令 ▶️ 启动 Hadoop ⏹️ 关闭 Hadoop &#x1f9fe; 查看进程是否正常运行 &#x1f4c1; 二、HDFS 常用文件系统指令 &#x1f6e0;️ 三、MapReduce 作业运行指令 &#x1f4cb; 四、集群状态监控指令 &#x1f4a1; …

【MySQL数据库】事务

目录 1&#xff0c;事务的详细介绍 2&#xff0c;事务的属性 3&#xff0c;事务常见的操作方式 1&#xff0c;事务的详细介绍 在MySQL数据库中&#xff0c;事务是指一组SQL语句作为一个指令去执行相应的操作&#xff0c;这些操作要么全部成功提交&#xff0c;对数据库产生影…

一、OrcaSlicer源码编译

一、下载 1、OrcaSlicer 2.3.0版本的源码 git clone https://github.com/SoftFever/OrcaSlicer.git -b v2.3.0 二、编译 1、在OrcaSlicer目录运行cmd窗口&#xff0c;输入build_release.bat 2、如果出错了&#xff0c;可以多运行几次build_release.bat 3、在OrcaSlicer\b…

港口危货储存单位主要安全管理人员考试精选题目

港口危货储存单位主要安全管理人员考试精选题目 1、危险货物储存场所的电气设备应符合&#xff08; &#xff09;要求。 A. 防火 B. 防爆 C. 防尘 D. 防潮 答案&#xff1a;B 解析&#xff1a;港口危货储存单位存在易燃易爆等危险货物&#xff0c;电气设备若不防爆&…

格雷希尔用于工业气体充装站的CZ系列气罐充装转换连接器,其日常维护有哪些

格雷希尔气瓶充装连接器&#xff0c;长期用于压缩气体的快速充装和压缩气瓶的气密性检测&#xff0c;需要进行定期的维护&#xff0c;为每一次的充装提供更好的连接。下列建议的几点维护准则适用于格雷希尔所有充注接头&#xff0c;请非专业人士不要随意拆卸连接器。 格雷希尔气…

Java 多线程进阶:什么是线程安全?

在多线程编程中&#xff0c;“线程安全”是一个非常重要但又常被误解的概念。尤其对于刚接触多线程的人来说&#xff0c;不理解线程安全的本质&#xff0c;容易写出“偶尔出错”的代码——这类 bug 往往隐蔽且难以复现。 本文将用尽可能通俗的语言&#xff0c;从三个角度解释线…

MSO-Player:基于vlc的Unity直播流播放器,支持主流RTSP、RTMP、HTTP等常见格式

MSO-Player 基于libVLC的Unity视频播放解决方案 支持2D视频和360度全景视频播放的Unity插件 &#x1f4d1; 目录 &#x1f3a5; MSO-Player &#x1f4cb; 功能概述&#x1f680; 快速入门&#x1f4da; 关键组件&#x1f4dd; 使用案例&#x1f50c; 依赖项&#x1f4cb; 注意…

navicat中导出数据表结构并在word更改为三线表(适用于navicat导不出doc)

SELECTCOLUMN_NAME 列名,COLUMN_TYPE 数据类型,DATA_TYPE 字段类型,IS_NULLABLE 是否为空,COLUMN_DEFAULT 默认值,COLUMN_COMMENT 备注 FROMINFORMATION_SCHEMA.COLUMNS WHEREtable_schema db_animal&#xff08;数据库名&#xff09; AND table_name activity&#xff08;…

docker学习笔记6-安装wordpress

一、创建自定义网络、查看网络 docker netword create blog docker network ls 二、 启动mysql容器 启动命令&#xff1a; docker run -d -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD123456 \ -e MYSQL_DATABASEwordpress \ -v mysql-data:/var/lib/mysql \ -v /app/myconf:/etc…