【C++算法】线性DP详解:数字三角形、最长上升子序列、最长公共子序列、最长公共子串、字符串编辑距离

文章目录

    • 1)数字三角形
      • 1:顺推
      • 2:逆推
    • 2)最长上升子序列
      • 1:线性DP做法
      • 2:二分优化
    • 3)最长公共子序列
    • 4)最长公共子串
    • 5)字符串编辑距离

1)数字三角形

1:顺推

  • 顺推比较需要注意的问题就是边界问题,因为从上往下推每个元素会用到上方元素和左上方元素
    • 对于某一行的最后一个元素,那么上方的元素是没有被初始化的
    • 对于某一行的第一个元素,那么左上方的元素是没有被初始化的
    • 为了保证这两种情况一定不选择未被初始化的元素,所以首先把 f f f 数组初始化为 − I N F -INF INF
  • 随后把 f [ 1 , 1 ] f[1,1] f[1,1] 初始化为 a [ 1 , 1 ] a[1,1] a[1,1],因为从第二行开始计算,这样计算出来的值就是正常值,最后从最后一行的出口中枚举找一个最大值
#include<bits/stdc++.h>
#define x first
#define y secondusing namespace std;typedef long long ll;
typedef pair<int,int> PII;// 解题思路: const int N=5e2+5;
const int INF=1e9;
int f[N][N];
int a[N][N];
int n;int main() {cin>>n;for(int i=1;i<=n;i++) {for(int j=1;j<=i;j++) {scanf("%d",&a[i][j]);}	}// 如果顺推,每个元素应该考虑上方和左上方元素// 如果当前计算元素的[i,j]刚好i==j即最后一个时,则上方无元素,会遇到边界问题// 为了一定不选择这个边界,可以把其初始化为-INF(因为三角形中数字可能有负值)// 别忘了左上角,所以ij均从0开始for(int i=0;i<=n;i++) {for(int j=0;j<=i+1;j++) {f[i][j]=-INF;}}f[1][1]=a[1][1]; // 从a[1][1]开始算,边界依然为-INF// 从第二行开始for(int i=2;i<=n;i++) {for(int j=1;j<=i;j++) {f[i][j]=max(f[i-1][j]+a[i][j],f[i-1][j-1]+a[i][j]);}	}int ans=INT_MIN;// 对出口求最大值,即为最大路径数字和for(int i=1;i<=n;i++) {ans=max(ans,f[n][i]);}cout<<ans;return 0;
}

2:逆推

  • 从上往下有五个出口,最终要用 O ( n ) O(n) O(n) 的时间来判断谁的值更大,如果从下往上那么出口只有一个,无需比较;并且从下往上逆推不会遇到边界问题,用到的每个元素都刚好有初始值,可以手动模拟一下为什么没有边界问题
#include<bits/stdc++.h>
#define x first
#define y secondusing namespace std;typedef long long ll;
typedef pair<int,int> PII;// 解题思路: 经典数字三角形const int N=5e2+5;
const int INF=1e9;
int n;
int a[N][N];int main() {// 逆推,从下往上那么出口只有一个,注意元素只从下方和右下方来// 从下往上没有边界问题cin>>n;for(int i=1;i<=n;i++) {for(int j=1;j<=i;j++) {scanf("%d",&a[i][j]);}	}// 从倒数第二行开始for(int i=n-1;i>=1;i--) {// 每一行的元素的个数应该就是ifor(int j=1;j<=i;j++) {a[i][j]+=max(a[i+1][j],a[i+1][j+1]);}}cout<<a[1][1]<<endl;return 0;
}
  • 若需要输出路径,可以用 b b b 数组 m e m c p y memcpy memcpy 原二维数组,因为加和是直接在原数组上进行操作的,另外用 p p p 表示前驱数组用来记录路径,在记录时只需要记录在列方向的偏移量即可,比如往右下则 p [ i , j ] = 1 p[i,j]=1 p[i,j]=1,往下 p [ i , j ] = 0 p[i,j]=0 p[i,j]=0
#include<bits/stdc++.h>
#define x first
#define y secondusing namespace std;typedef long long ll;
typedef pair<int,int> PII;// 解题思路: 经典数字三角形const int N=5e2+5;
const int INF=1e9;
int n;
int a[N][N];int p[N][N]; // 记录最大值路径
int b[N][N]; // 备份数组,路径跟踪int main() {// 逆推,从下往上那么出口只有一个,注意元素只从下方和右下方来// 从下往上没有边界问题cin>>n;for(int i=1;i<=n;i++) {for(int j=1;j<=i;j++) {scanf("%d",&a[i][j]);}	}// 拷贝memcpy(b,a,sizeof a); // 从a拷到b// 从倒数第二行开始for(int i=n-1;i>=1;i--) {// 每一行的元素的个数应该就是ifor(int j=1;j<=i;j++) {
//			a[i][j]+=max(a[i+1][j],a[i+1][j+1]);if(a[i+1][j]>=a[i+1][j+1]) {a[i][j]+=a[i+1][j];p[i][j]=0; // 来自下方,y轴增量为0} else {a[i][j]+=a[i+1][j+1];p[i][j]=1; // 来自右下,y轴增量为1}}}cout<<a[1][1]<<endl;int i,j;// 输出最大数的路径(行数一直增大,列数根据存储的增量变化)for(i=1,j=1;i<=n-1;i++) {cout<<b[i][j]<<"->";j+=p[i][j];}cout<<b[n][j];return 0;
}

2)最长上升子序列

1:线性DP做法

时间复杂度: O ( n 2 ) O(n^2) O(n2)

  • 如果不理解状态转移方程,可以 E03 线性DP 最长上升子序列 bilibili 4 : 00 4:00 4:00 起看该问题的模拟过程
#include<bits/stdc++.h>
#define x first
#define y secondusing namespace std;typedef long long ll;
typedef pair<int,int> PII;// 解题思路: const int N=1e5+5;
int a[N];
int f[N]; // 以第i个元素结尾的LIS(最长上升子序列)长度
int n;int main() {cin>>n;for(int i=1;i<=n;i++) {scanf("%d",&a[i]);	}int res=INT_MIN;for(int i=1;i<=n;i++) {f[i]=1; // 所有元素起码可以以自身结尾// 遍历i之前的元素,如果比i小则可以拼接for(int j=1;j<=i;j++) {// 不理解可以看视频中的模拟过程if(a[j]<a[i]) {f[i]=max(f[i],f[j]+1);}}res=max(res,f[i]);}cout<<res<<endl;return 0;
}

2:二分优化

时间复杂度: O ( n l o g n ) O(nlog^n) O(nlogn),因为二分查找是 O ( l o g n ) O(log^n) O(logn)

  • 模拟过程在 E04 线性DP 最长上升子序列 二分优化 bilibili 6 : 15 6:15 6:15
  • 唯一比较疑惑的地方在于,为什么是找到第一个大于等于 a [ i ] a[i] a[i] 的元素做替换而不是大于呢?翻了一下评论区搞明白了,比如 { 1 2 6 7 2 3 } \{1\ 2\ 6\ 7\ 2\ 3\} {1 2 6 7 2 3} 的话,如果大于 x x x,那么序列中可能出现重复元素,最长上升子序列为 1 2 2 3 1\ 2\ 2\ 3 1 2 2 3,这样就不是严格单调递增的了
#include<bits/stdc++.h>
#define x first
#define y secondusing namespace std;typedef long long ll;
typedef pair<int,int> PII;// 解题思路: const int N=1e5+5;
int a[N];
int b[N]; // 有序子序列
int len;
int n;int main() {cin>>n;for(int i=1;i<=n;i++) {scanf("%d",&a[i]);	}// 遍历a中每一个元素,构造有序子序列len=1;b[1]=a[1];// 1)如果a中元素大于b中最后一个元素,则添加到末尾// 2)如果a中元素小于等于b中最后一个元素,则在b数组中找到第一个大于等于a的元素进行替换// 比如a[i]替换掉b[j]后,b[j]变小,则b[1...j]的结尾元素更小,则更可能续其他元素,使ILS更大for(int i=2;i<=n;i++) {if(a[i]>b[len]) {b[++len]=a[i];} else {// 用二分找到第一个大于等于a[i]的元素(答案在左边,压缩右边界)int l=1,r=len;while(l<=r) {int mid=l+r>>1;if(b[mid]>=a[i]) {r=mid-1;} else {l=mid+1;}}// l是答案b[l]=a[i];}}// 最终len的长度就是答案cout<<len<<endl;return 0;
}

3)最长公共子序列

  • 为什么没有 a [ i ] ≠ b [ j ] a[i]≠b[j] a[i]=b[j],且 a [ i ] , b [ j ] a[i],\ b[j] a[i], b[j] 都不在公共子序列的情况?其实可以把这种情况归为第 2 , 3 2,\ 3 2, 3 种情况之一

在这里插入图片描述

  • 一边 d p dp dp 一边打标记记录状态转移,其中从左上方转移过来的元素即为 L C S LCS LCS 中的公共元素

在这里插入图片描述

  • 只要理解了状态转移方程,代码就很简单
#include<bits/stdc++.h>
#define x first
#define y secondusing namespace std;typedef long long ll;
typedef pair<int,int> PII;// 解题思路: const int N=1e3+5; // 字符串最大长度
int f[N][N]; // f[i][j]:序列a[1...i]和b[1...j]的最长公共子序列的长度(LCS)
char a[N],b[N];
int n,m;int main() {cin>>a+1>>b+1; // 从下标1开始存储n=strlen(a+1); // 起始位置是a+1m=strlen(b+1);// 初始化 f[0][j]=0,f[i][0]=0,即i和j中有未指向任意元素的指针存在时// 但是全局变量本身初始化为0,所以无需初始化// 枚举字符串afor(int i=1;i<=n;i++) {// 枚举字符串bfor(int j=1;j<=m;j++) {if(a[i]==b[j]) {f[i][j]=f[i-1][j-1]+1;} else {f[i][j]=max(f[i-1][j],f[i][j-1]);}}}cout<<f[n][m];return 0;
}
  • 如果要带路径输出呢?同理,开一个数组 p p p 用来记录取得 L C S LCS LCS 的路径,注意,只有来自左上方的元素是 L C S LCS LCS 中的元素
#include<bits/stdc++.h>
#define x first
#define y secondusing namespace std;typedef long long ll;
typedef pair<int,int> PII;// 解题思路: const int N=1e3+5; // 字符串最大长度
int f[N][N]; // f[i][j]:序列a[1...i]和b[1...j]的最长公共子序列的长度(LCS)
char a[N],b[N];
int p[N][N]; // 前驱数组
int n,m;int main() {cin>>a+1>>b+1; // 从下标1开始存储n=strlen(a+1); // 起始位置是a+1m=strlen(b+1);// 初始化 f[0][j]=0,f[i][0]=0,即i和j中有未指向任意元素的指针存在时for(int i=1;i<=n;i++) {for(int j=1;j<=m;j++) {if(a[i]==b[j]) {f[i][j]=f[i-1][j-1]+1;p[i][j]=1; // 来自↖} else if(f[i-1][j]>f[i][j-1]) {f[i][j]=f[i-1][j];p[i][j]=2; // 来自←} else {f[i][j]=f[i][j-1];p[i][j]=3; // 来自↑}}}cout<<f[n][m]<<endl; // 最长长度int i=n,j=m,k=f[n][m];vector<char> path;// i或j中任意一个元素=0时退出while(i>0 && j>0) {// 左上方if(p[i][j]==1) {path.push_back(a[i]); // LCS中i--,j--;} // 上方else if(p[i][j]==2) {i--;}// 左方else {j--;}}reverse(path.begin(),path.end());for(auto x:path) {cout<<x<<' ';}cout<<endl;return 0;
}

4)最长公共子串

  • 这一题和上一题有什么区别呢?序列可以是不连续的,但是串一定是连续的,区别就在此
  • 最长公共子序列中 f [ i , j ] f[i,j] f[i,j] 表示序列 a [ 1... i ] a[1...i] a[1...i] b [ 1... j ] b[1...j] b[1...j] 的最长公共子序列的长度
  • 最长公共子串中 f [ i , j ] f[i,j] f[i,j] 表示以 a [ i ] a[i] a[i] b [ j ] b[j] b[j] 为结尾的公共子串的长度

在这里插入图片描述

在这里插入图片描述

  • 则可以得到状态转移方程
    • a [ i ] = = b [ j ] a[i]==b[j] a[i]==b[j],构成公共子串, f [ i , j ] = f [ i − 1 , j − 1 ] + 1 f[i,j]=f[i-1,j-1]+1 f[i,j]=f[i1,j1]+1
    • a [ i ] ! = b [ j ] a[i]!=b[j] a[i]!=b[j],不能构成公共子串, f [ i , j ] = 0 f[i,j]=0 f[i,j]=0(为什么不记录为最大值呢?因为串必须是连续的)
#include<bits/stdc++.h>
#define x first
#define y secondusing namespace std;typedef long long ll;
typedef pair<int,int> PII;// 解题思路: const int N=1e3+5;
int n,m;
char a[N],b[N];
int f[N][N]; // 以a[i]和b[j]结尾的最长公共子串的长度int main() {cin>>a+1>>b+1;n=strlen(a+1);m=strlen(b+1);// 无需初始化,全局变量int ans=INT_MIN;for(int i=1;i<=n;i++) {for(int j=1;j<=m;j++) {// 必须要连续才相加if(a[i]==b[j]) {f[i][j]=f[i-1][j-1]+1;} else {f[i][j]=0;}ans=max(ans,f[i][j]);} }// 以最后一个元素结尾的不一定是最长公共子串的长度cout<<ans<<endl;return 0;
}

5)字符串编辑距离

  • f [ i , j ] f[i,j] f[i,j] 表示从 a [ 1... i ] a[1...i] a[1...i] 变成 b [ 1... j ] b[1...j] b[1...j] 的编辑距离
  • a [ i ] = b [ j ] a[i]=b[j] a[i]=b[j] f [ i , j ] = f [ i − 1 , j − 1 ] f[i,j]=f[i-1,j-1] f[i,j]=f[i1,j1] :因为新位置 i i i j j j 的元素是相等的,无需编辑转移
  • a [ i ] ! = b [ j ] a[i]!=b[j] a[i]!=b[j]
    • 修改,即 a a a 中前 i − 1 i-1 i1 项 和 b b b 中前 j − 1 j-1 j1 项已然相等,只需要把最后一项修改为 b [ j ] b[j] b[j] 即可,所以有 f [ i , j ] = f [ i − 1 , j − 1 ] + 1 f[i,j]=f[i-1,j-1]+1 f[i,j]=f[i1,j1]+1
    • 插入,即 a a a 中前 i i i 项和 b b b 中前 j − 1 j-1 j1 项相等,只需要再插入一项 b [ j ] b[j] b[j] 即可,所以有 f [ i , j ] = f [ i , j − 1 ] + 1 f[i,j]=f[i,j-1]+1 f[i,j]=f[i,j1]+1
    • 删除,即 a a a 中前 i − 1 i-1 i1 项和 b b b 中前 j j j 项相等,但是多了一项,所以有 f [ i , j ] = f [ i − 1 , j ] + 1 f[i,j]=f[i-1,j]+1 f[i,j]=f[i1,j]+1
    • 由于属性是取最小值,所以三者中取 m i n min min 即可

在这里插入图片描述

  • 二维数组的常规做法如下,关于滚动数组优化这里不做解释,因为自己都搞得不是很清楚
#include<bits/stdc++.h>
#define x first
#define y secondusing namespace std;typedef long long ll;
typedef pair<int,int> PII;// 解题思路: const int N=1e3+5;
int n,m;
char a[N],b[N];
int f[N][N];int main() {cin>>a+1>>b+1;n=strlen(a+1);m=strlen(b+1);// 从a[1...i]变成空串,需要是删除i次for(int i=1;i<=n;i++) {f[i][0]=i;}// 从空串变成b[1...j],需要添加j次for(int j=1;j<=m;j++) {f[0][j]=j;}// 状态转移// 如果记录一下状态转移就可以输出变化过程for(int i=1;i<=n;i++) {for(int j=1;j<=m;j++) {// 末尾相等,无需添加if(a[i]==b[i]) {f[i][j]=f[i-1][j-1];} else {f[i][j]=min(f[i-1][j],min(f[i][j-1],f[i-1][j-1]))+1; // 三种操作的最小值}}	}cout<<f[n][m]<<endl;return 0;
}

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

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

相关文章

股票高胜率的交易法则是什么?

股票交易中的高胜率交易法则并非一成不变&#xff0c;而是根据市场状况、个人投资风格和经验等多种因素综合而定的。以下是一些有助于提升交易胜率的法则和策略&#xff1a; 1.趋势跟踪法则&#xff1a;在股票交易中&#xff0c;趋势跟踪是一种有效的策略。通过观察大盘和个股…

Hadoop安装部署-NameNode高可用版

Hadoop分布式文件系统支持NameNode的高可用性&#xff0c;本文主要描述NameNode多节点高可用性的安装部署。 如上所示&#xff0c;Hadoop分布式文件系统部署了NameNode的Master主节点以及NameNode的Slave副节点&#xff0c;当Master主节点发生故障变得不可用时&#xff0c;ZooK…

llama-factory SFT系列教程 (一),大模型 API 部署与使用

文章目录 背景简介难点 前置条件1. 大模型 api 部署下一步阅读 背景 本来今天没有计划学 llama-factory&#xff0c;逐步跟着github的文档走&#xff0c;发现这框架确实挺方便&#xff0c;逐渐掌握了一些。 最近想使用 SFT 微调大模型&#xff0c;llama-factory 是使用非常广泛…

python之文件操作与管理

1、文件操作 通过open&#xff08;&#xff09;操作&#xff0c;来创建文件对象&#xff0c;下面是open&#xff08;&#xff09;函数语法如下&#xff1a; open&#xff08;file,mode r,buffering -1 , encoding None ,errors None , newline None,closefd True,opener …

Python(3):条件语句+循环语句+逻辑运算符+符号优先级

文章目录 一、if语句1.if语句2.if 和 elif区别3.三元表达式 二、循环语句1.range函数和循环结束关键字2.while循环3.for循环 三、逻辑运算符1.and语句2.or语句3.not语句4.逻辑运算法的优先级 四、python运算符优先级和结合性一览表 一、if语句 1.if语句 1.if单分支语句 格式…

Python项目1 外星人入侵_外星人

在本章中&#xff0c;我们将在游戏《外星人入侵》中添加外星人。首先&#xff0c;我们在屏幕上边缘附近添加一个外星人&#xff0c;然后生成一群外星人。我们让这群外星人向两边和下面移 动&#xff0c;并删除被子弹击中的外星人。最后&#xff0c;我们将显示玩家拥有的飞船数量…

从路由器syslog日志监控路由器流量

路由器是关键的网络基础设施组件&#xff0c;需要随时监控&#xff0c;定期监控路由器可以帮助管理员确保路由器通信正常。日常监控还可以清楚地显出通过网络的流量&#xff0c;通过分析路由器流量&#xff0c;安全管理员可及早识别可能发生的网络事件&#xff0c;从而避免停机…

算法训练营第二十三天(二叉树完结)

算法训练营第二十三天&#xff08;二叉树完结&#xff09; 669. 修剪二叉搜索树 力扣题目链接(opens new window) 题目 给定一个二叉搜索树&#xff0c;同时给定最小边界L 和最大边界 R。通过修剪二叉搜索树&#xff0c;使得所有节点的值在[L, R]中 (R>L) 。你可能需要改…

SQLite数据库在Linux系统上的使用

SQLite是一个轻量级的数据库解决方案&#xff0c;它是一个嵌入式的数据库管理系统。SQLite的特点是无需独立的服务器进程&#xff0c;可以直接嵌入到使用它的应用程序中。由于其配置简单、支持跨平台、服务器零管理&#xff0c;以及不需要复杂的设置和操作&#xff0c;SQLite非…

共享低碳未来!科士达新一代工商业储能产品引爆ESIE 2024

4月11日&#xff0c;第十二届储能国际峰会暨展览会&#xff08;ESIE 2024&#xff09;在北京首钢会展中心盛大开幕&#xff0c;科士达以“数智光储&#xff0c;共享低碳未来”为主题&#xff0c;携多款工商业储能产品及解决方案惊艳亮相本次盛会。 展会首日&#xff0c;科士达展…

C/C++基础----运算符

算数运算符 运算符 描述 例子 两个数字相加 两个变量a b得到两个变量之和 - 两个数字相减 - * 两个数字相乘 - / 两个数字相除 - % 两个数字相除后取余数 8 % 3 2 -- 一个数字递减 变量a&#xff1a;a-- 、--a 一个数字递增 变量a: a 、 a 其中递…

VSCode中调试C++程序

目录 一、准备工作&#xff1a;安装插件 1、C/C插件 ​编辑 2、CMake插件 3、CMake tool插件 二、调试过程 1、debug 2、打断点 3、调C/C文件 每次重新调试的时候都忘了具体步骤&#xff0c;直接给自己写个备忘录好了。 一、准备工作&#xff1a;安装插件 1、C/C插件…

zabbix“专家坐诊”第236期问答

问题一 Q&#xff1a;我的trap里已经可以收到信息了&#xff0c;后续要怎么创建监控项呀&#xff1f; A&#xff1a;参考&#xff1a; 问题二 Q&#xff1a;snmp和snmp trap咋搞&#xff1f; A&#xff1a;你指的是如何开启这些协议还是如何做监控项&#xff1f; Q&#xff1…

Opentelemetry——Observability Primer

Observability Primer 可观测性入门 Core observability concepts. 可观测性核心概念。 What is Observability? 什么是可观测性&#xff1f; Observability lets us understand a system from the outside, by letting us ask questions about that system without know…

Java——数组练习

目录 一.数组转字符串 二.数组拷贝 三.求数组中元素的平均值 四.查找数组中指定元素(顺序查找) 五.查找数组中指定元素(二分查找) 六.数组排序(冒泡排序) 七.数组逆序 一.数组转字符串 代码示例&#xff1a; import java.util.Arrays int[] arr {1,2,3,4,5,6}; String…

数据分析——数据规范化

数据规范化是数据分析中的一个重要步骤&#xff0c;其目的在于确保数据的一致性和可比性&#xff0c;提高数据质量和分析结果的准确性。以下是一些数据规范化的常见方法和技术&#xff1a; 数据清洗&#xff1a;此步骤主要清除数据中的重复项、空格、格式错误等&#xff0c;确…

批归一化(BN)在神经网络中的作用与原理

文章目录 1. 批归一化&#xff08;BN&#xff09;在神经网络中的作用与原理1.1 作用与优势1.2 原理与推导 2. 将BN应用于神经网络的方法2.1 训练时的BN 2. 将BN应用于神经网络的方法2.1 训练时的BN2.2 测试时的BN代码示例&#xff08;Python&#xff09;&#xff1a; 3. BN的优…

编程规范(保姆级教程)

文章目录 为什么需要编程规范&#xff1f;&#x1f4a1;代码检测工具 ESLint&#x1f4a1;代码格式化 Prettier&#x1f4a1;ESLint 与 Prettier 配合解决代码格式问题eslint支持ts约定式提交规范Commitizen助你规范化提交代码什么是 Git Hooks使用 husky commitlint 检查提交…

探索设计模式的魅力:MVVM模式在AI大模型领域的创新应用-打破传统,迎接智能未来

​&#x1f308; 个人主页&#xff1a;danci_ &#x1f525; 系列专栏&#xff1a;《设计模式》 &#x1f4aa;&#x1f3fb; 制定明确可量化的目标&#xff0c;坚持默默的做事。 MVVM模式在AI大模型领域的创新应用-打破传统迎接智能未来 &#x1f680; “在人工智能的领域里&a…

【Entity Framework】如何使用EF中的生成值

【Entity Framework】如何使用EF中的生成值 文章目录 【Entity Framework】如何使用EF中的生成值一、概述二、默认值三、计算列四、设置主键五、显示配置值生成六、设置日期/时间值生成6.1 创建时间戳6.2 更新时间戳 七、替代值生成八、无值生成九、总结 一、概述 数据库列的值…