一、什么是最长公共子序列
要了解什么是最长公共子序列,我们要清楚两个概念:
字符子序列:指的是字符串中不一定连续但先后顺序一致的n个字符,即可以去掉字符串中的部分字符,但不可改变其前后顺序。如abcdefg中,acdg,bdf属于它的子序列,而bac,dbfg则不是,因为它们与字符串的字符顺序不一致。
公共子序列:如果序列C既是序列A的子序列,同时也是序列B的子序列,则称它为序列A和序列B的公共子序列。如对序列 1,3,5,4,2,6,8,7和序列 1,4,8,6,7,5 来说,序列1,8,7是它们的一个公共子序列。
那么,最长公共子序列我们就很容易理解,并且我们很容易看出,最长公共子序列不一定唯一,可能有多个长度相同的公共子序列。
二、代码思路分析
我们拿“ALCHEMIST”和“ALGORITHMS”这两个字符串为例,求他们的最长公共子序列。这里引用一张图表(图表来源附在文章末尾):
看不懂没关系,慢慢来。我们很容易看出,他的第一行,第一列分别是我们题目对应的两个字符串中的字符;
格子中的数字代表两个字符串从第一个字符到现在对应的这个字符这一段中,最长公共子序列的长度。比如列中的 L 与行中的 L 对应的格子中是 2 ,就证明列对应的子字符串“AL”与行对应的子字符串“AL”的最长公共子序列长度为 2 ;再比如列中的 T 与行中的 T 对应的格子是 4 ,就证明列对应的子字符串“ALGORIT”与行对应的子字符串”ALCHEMIST“的最长公共子序列长度为 4 。
了解了格子中填的是什么,我们来看一下怎么填。
刚开始我们得到的图表是空的,只有行和列对应的字符串,然后按照“从左往右,从上到下”的顺序依次填写,如果行和列对应的字符不同,就填这个格子左边和上边的值中较大的一个;如果行和列对应的字符相同,就填这个格子的左上角的格子中的值再加一,就可以得到这样的图表。
我们发现,图表的最后一行,最后一列的数字 5 就是题中所给两字符串的最长公共子序列的长度。具体为什么这么填写,仔细思考一下应该可以理解,也可以去看我文末附的视频链接。
三、状态转移方程
由上面的分析我们很容易得到它的状态转移方程是:
if(a==b){
s[row][col]=s[row-1][col-1]+1;
}else{
s[row][col]=max(s[row][col-1],s[row-1][col]);
}
四、例题
给定一个长度为 N 数组 a 和一个长度为 M 的数组 b。请你求出它们的最长公共子序列长度为多少。数组 a 和数组 b 的值需要自行输入,最后输出一个整数作为答案。
#include <iostream>
using namespace std;
int main()
{
// 请在此输入您的代码
const int maxn=999;
int a[maxn];
int b[maxn];
int dp[maxn][maxn];
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int j=1;j<=m;j++){
cin>>b[j];
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(a[i]==b[j]){
dp[i][j]=dp[i-1][j-1]+1;
}else{
dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
}
}
}
cout<<dp[n][m];
return 0;
}
五、参考内容
图表来源及参考视频链接:
最长公共子序列 - 动态规划 Longest Common Subsequence - Dynamic Programming_哔哩哔哩_bilibili