【算法】最长公共子序列(C/C++)

最长公共子序列(LCS,Longest Common Subsequence)问题简称(LCS),是动态规划里面里面的基础算法它的所解决的问题是,在两个序列中找到一个序列,使得它既是第一个序列的子序列,也是第二个序列的子序列,并且该序列长度最长。由下图中两个序列,我们可以看出来最长公共子序列为[s c r g]。

我们来举个“栗子”,比如序列A为“abcdef”,序列B为“bcef”,那么它的最长公共子序列为序列B,即:“bcef”,注意最长公共子序列不用保证每一个字符必须连续。那么我们一般的暴力做法是什么呢?首先我们先要确定一个参照序列,这里以A为例吧,首先我们需要确定公共子序列的头部,由于选择了A序列为参照序列,那么遍历A序列的每一个字符,把这个遍历的字符与B序列的每一个字符相比较,若相等,A序列遍历到下一个字符,在B序列的基础上再与B序列的下一个字符为起点继续进行比较,直到序列结束,然后再确定A序列的下一个字符为头部,以此类推,从这里面找一个最大的数,即是最长公共子序列的长度。像这样做法,我们的时间复杂度也要O(n^2*m)(n为序列A的长度,m为序列B的长度)。这样的时间复杂度在做题时必然会WA掉,也是面试官不想看到的,我们肯定会有更为优秀的算法,下面我们介绍动态规划的思想。


动态规划:

上面我们说到每次确定公共子序列的头部时,我们的A序列需要重新返回来遍历A序列与B序列寻找相同的字符。这样的操作我们在第一次遍历时就已经遍历过一次,只是没有记录结果,如果我能够把这个结果记录下来,那么下一次再遍历到这个状态我们可以直接拿来用,避免了重复计算,大大减少了计算量,从而减少了时间复杂度。那么我们如何进行记录这个状态呢,我们设一个二维数组dp[i][j],表示A序列的前i项与B序列的前j项所能构成的最长公共子序列长度。

dp[i][j]的状态转移方程分为两种,当A[i]==B[j]时dp[i][j]=max(dp[i][j],dp[i-1][j-1]+1);说明当时这两个字符相等,就等于A序列前一个字符跟B序列前一个字符这个状态+1。当A[i]!=B[j]时dp[i][j]=max(dp[i-1][j],dp[i][j-1]);若此时这两个字符不相等,那么就是A序列前一个字符跟B序列当前字符这个状态与B序列前一个字符跟A序列当前字符这个状态进行比较,哪一个大我当前dp[i][j]状态就从哪里转移。

 for(int i=1;i<=n;i++){for(int j=1;j<=m;j++){dp[i][j]=max(dp[i-1][j],dp[i][j-1]);if(A[i]==B[j])dp[i][j]=max(dp[i][j],dp[i-1][j-1]+1);}}

此时时间复杂度来到了O(n*m)(n为序列A的长度,m为序列B的长度),这样便可以解决大部分题目,有的题目还是解决不了的,对于更高级一点我们可以利用二分优化一下。时间复杂度便可以达到了O(nlog(n)),具体怎么实现下面我们讲解一下。


二分优化:

二分优化就是利用离散化操作,把两个数组通过映射为一个数组,在一个数组里面类似于求最长上升子序列操作,我们选择一个参照数组a,那么就要遍历数组b,考虑它的映射值大小与dp数组值得关系,其核心就一句口诀“大则添加,小则替换”。

解释一下什么意思。考虑新进来一个元素a[i]:

(1)大则添加:如果a[i]大于b[len],直接让b[++len]=a[i]。即b数组的长度增加1,而且添加了一个元素。

(2)小则替换:如果a[i]小于或等于b[len],就用a[i]替换掉b数组中第一个大于或等于a[i]的元素。

假设第一个大于a[i]的元素是b[j],那么用a[i]换掉b[j]后,会使得b[1...j]这个上升子序列的结尾元素更小。对于一个上升子序列,其结尾元素越小,越有利于续接其它元素,也就越可能变得更长,也就是说替换完使序列更有潜力,更容易接纳元素。

int a[105]={1,6,3,2,7,4,3,3,2};
int b[105];
int m=9;
int len=1;
b[1]=a[1];
int find(int x){//二分查找int L=1,R=len,mid;while(L<=R){mid=(l+r)>>1;if(x>b[mid])L=mid+1;else R=mid-1;}return L;
}for(int i=2;i<=n;i++){if(a[i]>b[len]){//大则添加b[++len]=a[i];}else{//小则替换j=find(a[i]);b[j]=a[i];}
}
printf("%d\n",len);
图解算法:

文字去描述二分优化的过程不太好描述跟理解,那么我们进行图解一下算法的实现过程,希望对大家有所帮助。

我们以数组A=[3,1,4,2],数组B为[2,1,3,4]为例,进行图解。

初始化:离散化操作,对数组A进行离散化处理,得到map映射数组,拿着这个映射数组去把B数组的映射数组求出来。

第一步:预处理部分做完了就要开始我们的真正的实现了。当前我们初始化了dp数组为无穷大,由于我们选取了数组A为参照数组,那么我们就去遍历数组B的映射数组,这里就用到了我们所说的口诀“大则添加,小则替换”,此时数组B的映射数组第一个为4,dp数组里面都是inf,4<inf,小则替换,我们就去dp数组里面寻找第一个大于等于4的位置,给它替换成4,很明显dp数组第一个位置(下标为0)由inf替换成4。

第二步:数组B的映射数组到了第二个数了(下标为1),dp里面此时有一个数了,当前遍历的数为2,2与当前dp位置上的数比较,2<4,小则替换,很明显把dp第一个位置上的数4替换成2。

第三步:此时遍历到第三个数(下标为2),当前数组B的映射数组的值为1,1与当前dp数组上的位置相比较,1<2,小则替换,则把2替换为1。

第四步:此时遍历到最后一个位置了,当前数组B的映射数组的值为3,3与dp数组上当前位置上的数进行比较,3>1,根据口诀大则添加,则把3加到当前dp位置后面,即把dp[1]=3。

最终dp的长度为2,那么最长公共子序列的长度的值为2。由此dp数组我们还可以得到最长公共子序列是哪一个序列,这样我们反推回去,当前dp[0]=1,dp[1]=3,1对应的映射为3,3对应的映射为4,那么我们所得到的最长公共子序列就是[3,4]。


原题链接:【模板】最长公共子序列 - 洛谷

题目描述

给出 1,2,…,n 的两个排列P1​ 和 P2​ ,求它们的最长公共子序列。

输入格式

第一行是一个数 n。

接下来两行,每行为 n 个数,为自然数 1,2,…,n 的一个排列。

输出格式

一个数,即最长公共子序列的长度。

输入 

5 
3 2 1 4 5
1 2 3 4 5

输出 

3

说明/提示

对于 50%的数据, n≤10^3;

对于 100%的数据,n≤10^5。


解题思路:

最长公共子序列有两种解法,分别是朴素解法和一种二分优化的解法,此题10^5,若用第一种朴素解法肯定会TLE,所以下面我们详细介绍第二种解法。

朴素解法(会TLE)

很明显我们去枚举序列1的每一位和序列2的每一位,如果两个数字相等,那么dp[i][j]=dp[i-1[j-1]+1。最后计算dp[n][n]即可。

代码实现:
#include<iostream>
using namespace std;
const int N=1005;
int dp[N][N],a1[N],a2[N],n;
int main()
{//dp[i][j]表示两个串从头开始,直到第一个串的第i位 //和第二个串的第j位最多有多少个公共子元素 cin>>n;for(int i=1;i<=n;i++)cin>>a1[i];for(int i=1;i<=n;i++)cin>>a2[i];for(int i=1;i<=n;i++)for(int j=1;j<=n;j++){dp[i][j]=max(dp[i-1][j],dp[i][j-1]);if(a1[i]==a2[j])dp[i][j]=max(dp[i][j],dp[i-1][j-1]+1);//因为更新,所以++; }cout<<dp[n][n]<<endl;;return 0;
}

优化解法

主要跟最长上升子序列的优化方法一样的,记住这句话就可以,“大则添加,小则替换”,这就是实现的思路,当此时要进入的值大于最长子序列的最后值就添加,若小于最长子序列的最后的值,则找到最长子序列中第一个大于此值的下标把它给替换掉。

代码实现:
#include<iostream>
using namespace std;
const int N=1e5+5;
int n,len=1;
int a[N],b[N],dp[N],map[N];//mapA映射B,相当于A数组当标准,操作B数组,压缩为一个数组,
int main(){cin>>n;for(int i=1;i<=n;i++)cin>>a[i],map[a[i]]=i;//map映射for(int i=1;i<=n;i++)cin>>b[i],dp[i]=0x3f3f3f;//初始无穷大for(int i=1;i<=n;i++){if(map[b[i]]>dp[len])dp[++len]=map[b[i]];//大则添加else dp[lower_bound(dp,dp+len,map[b[i]])-dp]=map[b[i]];//小的替换,lower_bound实现更简单}cout<<len<<endl;//最后输出长度即可return 0;
}

最长公共子序列(LCS)是算法动态规划之中最基础的部分,是每一位算法初学者的首选,也是数学之中必学的内容,文章尚有不足,若有错误的地方恳请各位大佬指出。

执笔至此,感触彼多,全文将至,落笔为终,感谢大家的支持。 

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

【计算机网络 - 基础问题】每日 3 题(十三)

✍个人博客&#xff1a;Pandaconda-CSDN博客 &#x1f4e3;专栏地址&#xff1a;http://t.csdnimg.cn/fYaBd &#x1f4da;专栏简介&#xff1a;在这个专栏中&#xff0c;我将会分享 C 面试中常见的面试题给大家~ ❤️如果有收获的话&#xff0c;欢迎点赞&#x1f44d;收藏&…

阿里史上最大规模开源发布,超GPT-4o 、Llama-3.1!

今天凌晨&#xff0c;阿里巴巴官宣了史上最大规模的开源发布&#xff0c;推出了基础模型Qwen2.5、专用于编码Qwen2.5-Coder和数学的Qwen2.5-Math。 这三大类模型一共有10多个版本&#xff0c;包括0.5B、1.5B、3B、7B、14B、32B和72B&#xff0c;适用于个人、企业以及移动端、P…

数字化转型的策略与执行路径

企业在明确数字化转型的目标并评估自身数字化能力之后&#xff0c;必须前瞻性地识别出实现这些目标所需的关键数字化能力。基于这些能力&#xff0c;企业应制定出一套数字化转型战略&#xff0c;确立短期和中长期的转型目标&#xff0c;确保数字技术投资带来价值&#xff0c;而…

vulhub搭建漏洞环境docker-compose up -d命令执行报错以及解决方法汇总

在利用vulhub靶场搭建环境进行漏洞复现时&#xff0c;我们通常要使用这一步命令&#xff1a; docker-compose up -d 但是经常报错&#xff0c;今天我们来说几个常见的报错以及解决方法&#xff1a; 1.报错提示&#xff1a; ERROR: Couldnt connect to Docker daemon at httpdoc…

MySQL_图形管理工具简介、下载及安装(超详细)

课 程 推 荐我 的 个 人 主 页&#xff1a;&#x1f449;&#x1f449; 失心疯的个人主页 &#x1f448;&#x1f448;入 门 教 程 推 荐 &#xff1a;&#x1f449;&#x1f449; Python零基础入门教程合集 &#x1f448;&#x1f448;虚 拟 环 境 搭 建 &#xff1a;&#x1…

如何确保Java程序分发后不被篡改?使用JNI对Java程序进行安全校验

前言 众所周知&#xff0c;Java/Kotlin编译后会编译成smali&#xff0c;使用Jadx这类的反编译工具或者Hook工具就能很轻松的把我们的软件安全校验给破解了。 为了防止这种情况发生&#xff0c;我们一般会将核心代码使用C编写&#xff0c;然后使用JNI技术&#xff0c;使用Java…

对接全国点餐api接口有哪些具体步骤

与第三方餐饮服务提供商进行接口对接可以按照以下步骤进行&#xff1a; 一、前期准备 1.明确需求&#xff1a; 确定你的业务目标和对接口的具体需求。例如&#xff0c;你是希望通过接口获取餐厅信息、菜品列表、价格、库存情况&#xff0c;还是实现订单提交、支付处理、配送…

WAN广域网技术--PPP和PPPoE

广域网基础概述 广域网&#xff08;Wide Area Network&#xff0c;WAN&#xff09;是一种覆盖广泛地区的计算机网络&#xff0c;它连接不同地理位置的计算机、服务器和设备。广域网通常用于连接不同城市、州或国家之间的网络&#xff0c;它通过互联网服务提供商&#xff08;ISP…

中泰免签,准备去泰国旅游了吗?《泰语翻译通》app支持文本翻译和语音识别翻译,解放双手对着说话就能翻译。

泰国是很多中国游客的热门选择&#xff0c;现在去泰国旅游更方便了&#xff0c;因为泰国对中国免签了。如果你打算去泰国&#xff0c;那么下载一个好用的泰语翻译软件是很有必要的。 简单好用的翻译工具 《泰语翻译通》App就是为泰国旅游设计的&#xff0c;它翻译准确&#x…

pg198-jesd204-phy阅读笔记

简介 介绍 JESD204 PHY IP核实现了JESD204的物理接口&#xff0c;简化在发送和接收核心之间共享串行收发器信息通道。此内核一般不单独使用&#xff0c;只能与JESD204或JESD204C内核结合使用&#xff08;目前不太懂这句话&#xff0c;因为我只看到与TX、RX IP核结合使用&#…

声网SDK脚本运行错误

文章目录 运行步骤无法运行.bat电脑出现警告--更改执行策略若无出现-更新power shell搜索最新版本的 PowerShell安装新版本 仍无法解决-手动下载第三方库 2024-9-9运行步骤 无法运行.bat 电脑出现警告–更改执行策略 若无出现-更新power shell 搜索最新版本的 PowerShell 在…

Java面试篇基础部分-Java线程生命周期

线程的生命周期分别为 新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)这五种状态。   在系统运行过程中有线程不断地被创建,而旧的线程在执行完毕之后被清理,线程通过排队的方式获取共享资源或者锁的时候被阻塞,所以运行中的线程就会在…

让医院更智慧,让决策更容易

依托数字孪生技术&#xff0c;赋能智慧医院&#xff0c;对使用者和决策者带来了众多的优势。数字孪生技术是将物理实体与数字模型相结合&#xff0c;实现实时监测、仿真预测和智能决策的一种先进技术。在智慧医院中应用数字孪生技术&#xff0c;不仅可以提升医疗服务的质量和效…

气势如神助!未来三年最好的投资:找适合自己的稳定的路——早读(逆天打工人爬取热门微信文章解读)

势如破竹&#xff01;&#xff01;&#xff01;冲 引言Python 代码第一篇 洞见 未来三年最好的投资&#xff1a;好好上班第二篇 趋势结尾 偷换概念&#xff0c;贝多芬失聪是后来的事了&#xff0c;从小失聪还能当音乐家怕不是觉醒松果体了 但体会这个意思&#xff0c;玩味一下即…

开放式耳机和入耳式耳机哪个好?2024优质开放式蓝牙耳机推荐

首先&#xff0c;开放式耳机与传统的入耳式耳机相比&#xff0c;最大的特点在于其的舒适性和对听力的保护。因为无需入耳&#xff0c;所以能够有效地减少长时间佩戴导致的耳朵疲劳&#xff0c;同时也避免了直接对耳膜的压迫。但是当然也会有问题出现&#xff0c;开放式耳机别人…

pprof简单使用

1. 什么是 pprof&#xff1f; pprof 是 Go 语言内置的性能分析工具。它能够帮助开发者收集 CPU、内存、goroutine 等资源的使用情况&#xff0c;生成性能报告并提供可视化功能。pprof 提供了全面的性能分析能力&#xff0c;是排查性能瓶颈、优化代码的利器。 2. pprof 使用场…

力扣309-买卖股票的最佳时机含冷冻期(Java详细题解)

题目链接&#xff1a;309. 买卖股票的最佳时机含冷冻期 - 力扣&#xff08;LeetCode&#xff09; 前情提要&#xff1a; 本题是由122. 买卖股票的最佳时机 II - 力扣&#xff08;LeetCode&#xff09;变形而来&#xff0c;122是可以买卖多次股票没有冷冻期&#xff0c;该题还…

重修设计模式-结构型-组合模式

重修设计模式-结构型-组合模式 Compose objects into tree structure to represent part-whole hierarchies.Composite lets client treat individual objects and compositions of objects uniformly. 将一组对象组织成树形结构&#xff0c;来表示一种“部分 - 整体”的层次结…

携手阿里云CEN:共创SD-WAN融合广域网

在9月19日举行的阿里云云栖大会上&#xff0c;犀思云作为SD-WAN领域的杰出代表及阿里云的SD-WAN重要合作伙伴&#xff0c;携手阿里云共同推出了创新的企业上云方案——Fusion WAN智连阿里云解决方案。这一创新方案不仅彰显了犀思云在SD-WAN技术领域的深厚积累&#xff0c;更体现…

骨传导耳机哪款好?精选2024五款高性能品牌推荐!

随着骨传导耳机越来越受欢迎&#xff0c;不仅运动健身的朋友人手一副&#xff0c;很多上班族和学生党也开始使用骨传导耳机。然而&#xff0c;由于很多人对骨传导耳机的了解还不够深入&#xff0c;所以在选购中经常会入手一些不专业的产品&#xff0c;这些劣质产品不仅音质效果…