【动态规划】求最长递增子序列问题

目录

问题描述

最长递增子序列(Longest Increasing Subsequence,LIS

  • 子序列:对于任意序列s,它的子序列是通过删除其中零个或多个元素得到的另⼀个序列
    注:剩余元素的相对顺序保持不变

给定n个整数组成的序列 s [ 1... n ] s[1...n] s[1...n],求最长递增子序列LIS(的长度)

83613547

递推关系

建立递推关系的思路

假设能够求出 s [ 1... k − 1 ] s[1...k-1] s[1...k1]LIS,考虑能否由此推出 s [ 1... k ] s[1...k] s[1...k]LIS

  • 如果仅知道长度
    • 无法判断 s [ k ] s[k] s[k] 能否让LIS变长
  • 如果不仅知道长度,还知道具体序列 L
    • s [ k ] s[k] s[k] 能让 L 变长,那问题就解决了
    • 也许 L 就是 s [ 1... k ] s[1...k] s[1...k]LIS
    • 也许存在 s [ 1... k ] s[1...k] s[1...k] 的另⼀个LIS:L‘, s [ k ] s[k] s[k] 能让L’变长
    • 可能需要记住 s [ 1... k − 1 ] s[1...k-1] s[1...k1] 的所有LIS

原始子问题: 令 L ( k ) L(k) L(k) 表示 s [ 1... k ] s[1...k] s[1...k]LIS的长度,原问题即求解 L ( n ) L(n) L(n)

  • O(n)个子问题,但不容易建立递归关系

约束条件:以 s [ k ] s[k] s[k] 结尾

  • L ( k ) L(k) L(k) 表示 s [ 1... n ] s[1...n] s[1...n] s [ k ] s[k] s[k] 结尾LIS的长度
  • 原问题即为求解 max ⁡ 1 ≤ k ≤ n L ( k ) \max_{1\le k\le n}L(k) max1knL(k)
  • 基本情况: 如果 k = 1 k = 1 k=1,那么 L ( k ) = 1 L(k) = 1 L(k)=1
  • 归纳步骤
    • L k = max ⁡ { 1 , max ⁡ 1 ≤ i ≤ k − 1 { L ( i ) + 1 ∣ s [ k ] > s [ i ] } } L_k = \max \{1, \max_{1\le i \le k-1} \{ L(i) +1 | s[k] > s[i] \}\} Lk=max{1,max1ik1{L(i)+1∣s[k]>s[i]}},其中, max ⁡ ⊘ \max \oslash max的值定义为0

在这里插入图片描述

  • 此时的递推关系:

L ( k ) = { 1 i f k = 1 max ⁡ { 1 , max ⁡ 1 ≤ i ≤ k − 1 { L ( i ) + 1 ∣ s [ k ] > s [ i ] } } i f k > 1 L(k) = \begin{cases} 1 &if\quad k=1\\ \max \{1, \max_{1\le i \le k-1} \{ L(i) +1 | s[k] > s[i] \}\} &if \quad k>1 \end{cases} L(k)={1max{1,max1ik1{L(i)+1∣s[k]>s[i]}}ifk=1ifk>1

  • O ( n ) O(n) O(n) 个子问题,每个子问题复杂度为 O ( k ) O(k) O(k)。时间复杂度为 O ( n 2 ) O(n^2) O(n2)

约束条件:以 s [ k ] s[k] s[k] 开头

  • L ( k ) L(k) L(k) 表示 s [ 1... n ] s[1...n] s[1...n] s [ k ] s[k] s[k] 开头LIS的长度
  • 原问题即为求解 max ⁡ 1 ≤ k ≤ n L ( k ) \max_{1\le k\le n}L(k) max1knL(k)
  • 基本情况: 如果 k = n k = n k=n,那么 L ( k ) = 1 L(k) = 1 L(k)=1

在这里插入图片描述

  • 此时的递推关系:

L ( k ) = { 1 i f k = n max ⁡ { 1 , max ⁡ k + 1 ≤ i ≤ n { L ( i ) + 1 ∣ s [ k ] < s [ i ] } } i f k < n L(k) = \begin{cases} 1 &if\quad k=n\\ \max \{1, \max_{k+1\le i \le n} \{ L(i) +1 | s[k] < s[i] \}\} &if \quad k<n \end{cases} L(k)={1max{1,maxk+1in{L(i)+1∣s[k]<s[i]}}ifk=nifk<n

  • O ( n ) O(n) O(n) 个子问题,每个子问题复杂度为 O ( k ) O(k) O(k)。时间复杂度为 O ( n 2 ) O(n^2) O(n2)

约束条件:增加子问题参数(前缀)

  • L ( i , j ) L(i,j) L(i,j) 表示 s [ j . . . n ] s[j...n] s[j...n] 中每个元素都大于 s [ i ] s[i] s[i] LIS的长度
  • s [ 0 ] = − ∞ s[0] =-\infty s[0]= ,原问题即求解 L ( 0 , 1 ) L(0,1) L(0,1)
  • 基本情况: 如果 j > n j> n j>n ,那么 L ( i , j ) = 0 L(i,j)= 0 L(i,j)=0

在这里插入图片描述

  • 归纳步骤
    • 如果 s [ i ] > s [ j ] s[i] > s[j] s[i]>s[j] L ( i , j ) = L ( i , j + 1 ) L(i,j) = L(i,j+ 1) L(i,j)=L(i,j+1)
    • 否则 L ( i , j ) = max ⁡ { L ( i , j + 1 ) , 1 + L ( j , j + 1 ) } L(i,j) = \max \{ L(i,j+ 1),1 + L(j,j+ 1)\} L(i,j)=max{L(i,j+1),1+L(j,j+1)}
  • O ( n 2 ) O(n^2) O(n2)个子问题,每个子问题求解复杂度为 O ( 1 ) O(1) O(1),时间复杂度: O(n2); 空间复杂度: O(n2)
  • 此时的递推关系:
    L ( i , j ) = { 0 i f j > n L ( i , j + 1 ) i f s [ i ] ≥ s [ j ] max ⁡ { L ( i , j + 1 ) 1 + L ( j , j + 1 ) o t h e r w i s e L(i,j) = \begin{cases} 0 &if\quad j>n\\ L(i,j+1) &if\quad s[i]\ge s[j] \\ \max \begin{cases} L(i,j+1) \\ 1+L(j,j+1) \end{cases} &otherwise \end{cases} L(i,j)= 0L(i,j+1)max{L(i,j+1)1+L(j,j+1)ifj>nifs[i]s[j]otherwise

在这里插入图片描述


约束条件:增加子问题参数(后缀)

  • L ( i , j ) L(i,j) L(i,j) 表示 s [ 1... j ] s[1...j] s[1...j] 中每个元素都小于 s [ i ] s[i] s[i] LIS的长度
  • s [ n + 1 ] = ∞ s[n+1] =\infty s[n+1]= ,原问题即求解 L ( n + 1 , n ) L(n+1,n) L(n+1,n)
  • 基本情况: 如果 j = 0 j=0 j=0 ,那么 L ( i , j ) = 0 L(i,j)= 0 L(i,j)=0

在这里插入图片描述

  • 归纳步骤

    • 如果 s [ i ] ≤ s [ j ] s[i] \le s[j] s[i]s[j] L ( i , j ) = L ( i , j − 1 ) L(i,j) = L(i,j- 1) L(i,j)=L(i,j1)
    • 否则 L ( i , j ) = max ⁡ { L ( i , j − 1 ) , 1 + L ( j , j − 1 ) } L(i,j) = \max \{ L(i,j- 1),1 + L(j,j- 1)\} L(i,j)=max{L(i,j1),1+L(j,j1)}
  • 此时的递推关系:
    L ( i , j ) = { 0 i f j = 0 L ( i , j − 1 ) i f s [ i ] ≤ s [ j ] max ⁡ { L ( i , j − 1 ) 1 + L ( j , j − 1 ) o t h e r w i s e L(i,j) = \begin{cases} 0 &if\quad j=0\\ L(i,j-1) &if\quad s[i]\le s[j] \\ \max \begin{cases} L(i,j-1) \\ 1+L(j,j-1) \end{cases} &otherwise \end{cases} L(i,j)= 0L(i,j1)max{L(i,j1)1+L(j,j1)ifj=0ifs[i]s[j]otherwise

在这里插入图片描述


约束条件:LIS长度为k且末尾元素最小

  • 对于长度为 k k k 的递增子序列,只需记住末尾元素最小的那个

  • 本质是寻找上限最高(可拓展性最强)的那个子序列

  • L ( k ) L(k) L(k) 表示 s [ 1... n ] s[1...n] s[1...n] 中长度为 k k k 且末尾元素最小的递增子序列,且 L ( k ) . l a s t L(k).last L(k).last 表示该序列中最后那个元素

  • 引理: L ( 1 ) . l a s t < L ( 2 ) . l a s t < . . . < L ( k ) . l a s t L(1) .last < L(2) .last < ... < L(k).last L(1).last<L(2).last<...<L(k).last

    • 假设 x ≥ y x \ge y xy,而 y ≥ z y \ge z yz,所以 x ≥ z x \ge z xz
    • 那么灰色元素构成一个长度为 k k k 且末尾元素最小的递增子序列,矛盾
      在这里插入图片描述
  • 归纳假设: 对长度小于 n n n 的序列,可以计算其所有的 L ( k ) L(k) L(k),并有序存储

  • 基本情况: 长度为1的序列,有 L [ 1 ] ← s [ 1 ] L[1]\leftarrow s[1] L[1]s[1]

  • 如何基于归纳假设求解 s [ 1.. n ] s[1..n] s[1..n] 的所有的 L ( k ) L(k) L(k)

    • L ( k ) . l a s t L(k).last L(k).last 构成的有序数组中查找插入位置 k ′ k' k,使得 s [ n ] s[n] s[n] 加入后仍然有序
    • 如果 k ′ = k + 1 k' =k+1 k=k+1,那么 L ( k + 1 ) + L ( k ) + 1 L(k + 1) + L(k) +1 L(k+1)+L(k)+1 L ( k + 1 ) . l a s t ← s [ n ] L(k + 1).last \leftarrow s[n] L(k+1).lasts[n]
    • 否则 L ( k ′ ) . l a s t ← s [ n ] L(k').last \leftarrow s[n] L(k).lasts[n],但 L ( k ′ ) L(k') L(k) 的值不变
  • 时间复杂度: O ( l o g n ) O(logn) O(logn)

在这里插入图片描述


运行实例

#include <iostream>
#include <vector>
#include <algorithm>
#include <set>
using namespace std;ostream& operator<<(ostream& os, const vector<int>& v) {for (auto e : v)os << e << ' ';return os;
}int lis_dp1(const vector<int>& s, int n) {vector<int> dp(n + 1, 1); // 初始化dp数组,dp[i]表示以s[i]结尾的LIS的长度for (int k = 2; k <= n; ++k) {for (int i = 1; i < k; ++i) {if (s[k] > s[i]) {dp[k] = max(dp[k], dp[i] + 1);}}}return *max_element(dp.begin(), dp.end()); // 返回dp数组中的最大值作为整个序列的最长递增子序列的长度
}int lis_dp2(const vector<int>& s, int n) {vector<vector<int>> dp(n + 2, vector<int>(n + 2, -1)); // 初始化dp数组,dp[i][j]表示L(i,j)for (int i = 0; i <= n + 1; ++i)dp[i][n + 1] = 0; // 基本情况:当 j > n 时,L(i,j) = 0for (int j = n; j >= 1; --j) {for (int i = 0; i < j; ++i) {if (s[i] >= s[j]) {dp[i][j] = dp[i][j + 1]; // 如果s[i] >= s[j],则L(i,j) = L(i,j+1)}else {dp[i][j] = max(dp[i][j + 1], 1 + dp[j][j + 1]); // 否则L(i,j) = max{L(i,j+1), 1+L(j,j+1)}}}}return dp[0][1]; // 返回L(0,1)作为整个序列的最长递增子序列的长度
}int lis_dp3(const vector<int>& s, int n) {if (n == 0) return 0;set<int> L;L.insert(s[1]);for (int i = 2; i < n+1; ++i) {if (s[i] > * L.rbegin()) {L.insert(s[i]);}else {L.erase(L.lower_bound(s[i]));L.insert(s[i]);}}return L.size();
}vector<int> find_lis_dp1(const vector<int>& s, int n) {vector<int> dp(n + 1, 1); // 初始化dp数组,dp[i]表示以s[i]结尾的LIS的长度vector<int> parent(n + 1, -1); // 记录每个元素的父节点索引for (int k = 2; k <= n; ++k) {for (int i = 1; i < k; ++i) {if (s[k] > s[i] && dp[k] < dp[i] + 1) {dp[k] = dp[i] + 1;parent[k] = i; // 更新父节点索引}}}int max_length = *max_element(dp.begin(), dp.end()); // 获取最长递增子序列的长度int max_index = distance(dp.begin(), find(dp.begin(), dp.end(), max_length)); // 获取最长递增子序列的结束索引vector<int> lis;while (max_index != -1) {lis.push_back(s[max_index]);max_index = parent[max_index]; // 根据父节点索引回溯}reverse(lis.begin(), lis.end()); // 反转得到正确顺序的最长递增子序列return lis;
}vector<int> find_lis_dp2(const vector<int>& s, int n) {vector<vector<int>> dp(n + 2, vector<int>(n + 2, -1)); // 初始化dp数组,dp[i][j]表示L(i,j)vector<vector<int>> parent(n + 2, vector<int>(n + 2, -1)); // 记录每个元素的父节点索引for (int i = 0; i <= n + 1; ++i)dp[i][n + 1] = 0; // 基本情况:当 j > n 时,L(i,j) = 0for (int j = n; j >= 1; --j) {for (int i = 0; i < j; ++i) {if (s[i] >= s[j]) {dp[i][j] = dp[i][j + 1];}else {dp[i][j] = max(dp[i][j + 1], 1 + dp[j][j + 1]);if (dp[i][j] == dp[j][j + 1] + 1) {parent[i][j] = j; // 更新父节点索引}}}}vector<int> lis;int i = 0, j = 1;while (j <= n) {if (dp[i][j] == dp[j][j + 1] + 1) {lis.push_back(s[j]);i = j;}++j;}return lis;
}vector<int> find_lis_dp3(const vector<int>& s, int n) {vector<int> lis;set<int> L;L.insert(s[1]);for (int i = 2; i < n + 1; ++i) {if (s[i] > *L.rbegin()) {L.insert(s[i]);}else {L.erase(L.lower_bound(s[i]));L.insert(s[i]);}}for (int num : L) {lis.push_back(num);}return lis;
}int main(int argc, const char* argv[]) {vector<int> s = { -1, 8, 3, 6, 1, 3, 5, 4, 7 }; // 注意s[0]仅作标识,真实数据为s[1]~s[n]cout << lis_dp1(s, s.size() - 1) << endl;cout << find_lis_dp1(s, s.size() - 1) << endl;cout << "------------------------------"  << endl;cout << lis_dp2(s, s.size() - 1) << endl;cout << find_lis_dp2(s, s.size() - 1) << endl;cout << "------------------------------" << endl;cout << lis_dp3(s, s.size() - 1) << endl;cout << find_lis_dp3(s, s.size() - 1) << endl;cout << "------------------------------" << endl;return 0;
}

运行结果:

在这里插入图片描述


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

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

相关文章

将图像的rgb数据转成DICOM医学图像格式

dcmtk官方文档&#xff1a;https://support.dcmtk.org/docs/ dcmtk最新源码下载&#xff1a;https://www.dcmtk.org/en/dcmtk/dcmtk-software-development/ dcmtk旧版本源码下载&#xff1a;https://dicom.offis.de/download/dcmtk/ 用DCMTK库实现将图像转成dcm格式 dcmtk库的…

C++二分查找、离线算法:最近的房间

作者推荐 利用广度优先或模拟解决米诺骨牌 本文涉及的基础知识点 二分查找算法合集 题目 一个酒店里有 n 个房间&#xff0c;这些房间用二维整数数组 rooms 表示&#xff0c;其中 rooms[i] [roomIdi, sizei] 表示有一个房间号为 roomIdi 的房间且它的面积为 sizei 。每一…

mitmproxy安装以及模拟接口数据返回

使用pycharm直接安装&#xff0c;pip install mitmproxy 安装成功后直接使用命令mitmdump --version查看版本 然后自己本地下载https://mitmproxy.org/downloads/#10.1.1/ 之后一步步安装即可 安装成功后这里会出现一个.mitmproxy文件 双击这个文件进入开始安装证书 我…

【闲读 1】量子论引出对认知的思考

文章目录 一、物理学的两朵乌云故事量子力学的世界 二、 波粒二象性三、量子不确定性四、感知尺度 混沌学院课程《【物理学思维】第四节 量子论》&#xff0c;观后感。 一、物理学的两朵乌云故事 19世纪末&#xff0c;著名的物理学家开尔文爵士&#xff08;温度单位命民&…

vue2通过权限控制tab标签显示和隐藏

vue2通过权限控制tab标签显示和隐藏 1、前言2、v-if实现自定义控制 1、前言 在开发过程中&#xff0c;我们可能会遇到这样一个场景&#xff1a;根据不同权限对tab栏内容进行控制&#xff0c;这时候用自定义指令v-permission就达不到我们想要的效果&#xff0c;其是将当前节点的…

图书管理系统源码,图书管理系统开发,图书借阅系统源码整体功能演示

用户登录 基础资料 操作员管理 超期罚金设置 读者分类 读者管理 图书分类 图书管理 图书借还管理 图书借取 图书还去 图书借还查询 读者借书排行 用户登录 运行View目录下Login文件夹下的Index.csthml出现登录界面&#xff0c;输入用户名密码分别是admin密码admin12…

IDEA删除的文件怎么找回更新

一、 查找本地历史记录IDEA在进行代码版本管理时&#xff0c;会自动创建本地历史记录&#xff0c;如果我们误删了文件&#xff0c;可以通过查找本地历史记录来找回文件。 1.在项目中&#xff0c;选中被删文件的父级目录&#xff0c;“File”->“Local History”->“Show…

智能优化算法应用:基于头脑风暴算法无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于头脑风暴算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于头脑风暴算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.头脑风暴算法4.实验参数设定5.算法结果6.参考…

Linux:文件系统初步理解

文章目录 文件的初步理解C语言中对文件的接口系统调用的接口位图的理解open调用接口 文件和进程的关系进程和文件的低耦合 如何理解一切皆文件&#xff1f; 本篇总结的是关于Linux中文件的各种知识 文件的初步理解 在前面的文章中有两个观点&#xff0c;1. 文件 内容 属性&…

算法通关村-----数据流的中位数

数据流的中位数 问题描述 中位数是有序整数列表中的中间值。如果列表的大小是偶数&#xff0c;则没有中间值&#xff0c;中位数是两个中间值的平均值。 例如 arr [2,3,4] 的中位数是 3 。 例如 arr [2,3] 的中位数是 (2 3) / 2 2.5 。 实现 MedianFinder 类: MedianFin…

yolov8-seg 分割推理流程

目录 一、分割检测 二、图像预处理 二、推理 三、后处理与可视化 3.1、后处理 3.2、mask可视化 四、完整pytorch代码 一、分割检测 注&#xff1a;本篇只是阐述推理流程&#xff0c;tensorrt实现后续跟进。 yolov8-pose的tensorrt部署代码稍后更新&#xff0c;还是在仓…

探索数字化转型项目的基础

从消费品到特种化学品&#xff0c;数字化转型正在各行各业中逐渐普及。然而&#xff0c;尽管使用智能化设备、连接解决方案和数据分析对改造升级制造运营模式有巨大帮助&#xff0c;但起步过程&#xff08;奠定一个良好的基础来支撑工厂的可访问性、可靠性、可维护性、可扩展性…

java中IO知识点概念

这里写自定义目录标题 内存中的数据以电子信号的形式表示&#xff0c;而磁盘中的数据是以磁场的方向表示。1.流的分类2.File类3.流的API 关键4.理解缓冲的作用-一次性多拿些读写文件的时候为什么要有缓冲流 -意义是什么缓冲流的使用 5.路径问题6.文件的创建7.内存和磁盘存储本质…

【3D程序软件】SideFX与上海道宁一直为设计师提供程序化 3D动画和视觉效果工具,旨在创造高质量的电影效果

Houdini是一个 从头开始构建的程序系统 使艺术家能够自由工作 创建多次迭代 并与同事快速共享工作流程 Houdini FX为 视觉特效艺术家创作故事片 广告或视频游戏 凭借其基于程序节点的工作流程 Houdini FX可让 您更快地创建更多内容 从而缩短时间并 在所有创意任务中…

ESP Multi-Room Music 方案:支持音频实时同步播放 实现音乐互联共享

项目背景 随着无线通信技术的发展&#xff0c;针对不同音频应用领域的无线音频产品正不断涌现。近日&#xff0c;乐鑫科技推出了基于 Wi-Fi 的多扬声器互联共享音乐通信协议——ESP Multi-Room Music 方案。该方案使用乐鑫自研的基于 Wi-Fi 局域网的音频同步播放技术&#xff…

51单片机使用串口查看程序执行的数据

51单片机使用串口查看程序执行的数据 1.概述 这篇文章介绍利用串口输出程序执行的数据&#xff0c;辅助我们调试程序&#xff0c;提高代码定位问题的效率。 2.硬件电路原理 3.串口助手查看程序数据 输出串口数据的方式分为CPU查询方式和中断方式。他们各有优缺点&#xff0…

源码剖析 Spring Security 的实现原理

Spring Security 是一个轻量级的安全框架&#xff0c;可以和 Spring 项目很好地集成&#xff0c;提供了丰富的身份认证和授权相关的功能&#xff0c;而且还能防止一些常见的网络攻击。我在工作中有很多项目都使用了 Spring Security 框架&#xff0c;但基本上都是浅尝辄止&…

Java 8 中 ReentrantLock 与 Synchronized 的区别

&#x1f680; 作者主页&#xff1a; 有来技术 &#x1f525; 开源项目&#xff1a; youlai-mall &#x1f343; vue3-element-admin &#x1f343; youlai-boot &#x1f33a; 仓库主页&#xff1a; Gitee &#x1f4ab; Github &#x1f4ab; GitCode &#x1f496; 欢迎点赞…

《微信小程序开发从入门到实战》学习三十五

4.2 云开发JSON数据库 4.2.3 权限控制 在云开发控制台可以对数据库中的数据进行操作&#xff0c; 在小程序端和云函数可以分别使用小程序API和服务端API对数据中的数据进行操作。 以上操作受到权限控制。 对数据库进行查询属于读操作&#xff0c;增删改操作属于写操作。 …

Day44力扣打卡

打卡记录 给小朋友们分糖果 II&#xff08;容斥原理 隔板法&#xff09; 链接 def c2(n):return n * (n - 1) // 2 if n > 1 else 0class Solution:def distributeCandies(self, n: int, limit: int) -> int:return c2(n 2) - 3 * c2(n - limit 1) 3 * c2(n - 2 * …