最长公共子序列
给定两个序列,找出它们之间最长的公共子序列的长度。注意区分子序列和字串概念。
朴素做法思路:
-
一种朴素的算法是使用动态规划。首先,定义一个二维数组dp,其中
dp[i][j]表示第一个序列的前i个字符与第二个序列的前j个字符的最长公共子序列的长度
。为了求解dp[i][j],需要考虑以下两种情况:- 如果第一个序列的第i个字符与第二个序列的第j个字符相同,则这两个字符都应该包含在最长公共子序列中,因此可以将它们添加到最长公共子序列中,同时将
dp[i][j]
更新为dp[i-1][j-1]+1
。 - 如果第一个序列的第i个字符与第二个序列的第j个字符不同,则这两个字符至少有一个不包含在最长公共子序列中。因此,可以通过比较
dp[i-1][j]
和dp[i][j-1]
来确定dp[i][j]
的值。具体地,如果dp[i-1][j]
大于等于p[i][j-1]
,则最长公共子序列包含第一个序列的第i个字符,但不包含第二个序列的第j个字符。反之,如果dp[i-1][j]
小于dp[i][j-1]
,则最长公共子序列包含第二个序列的第j个字符,但不包含第一个序列的第i个字符。如果两者相等,则可以任选其一。
- 如果第一个序列的第i个字符与第二个序列的第j个字符相同,则这两个字符都应该包含在最长公共子序列中,因此可以将它们添加到最长公共子序列中,同时将
-
最终,
dp[n][m]
的值就是最长公共子序列的长度,其中n
和m
分别表示第一个序列和第二个序列的长度。 -
时间复杂度具体估算方法为
状态数量*状态转移
,此处一共n*m个状态,每次转移是O(1)的,故时间复杂度为O(nm)
。
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1010;
int n,m;
int f[N][N];
char a[N],b[N];//这里最好不要用string,因为用char我们可以自己决定读入的位置。方便处理f[i-1][j-1]的情况
int main()
{
cin>>n>>m>>(a+1)>>(b+1);//字符数组名表示了该数组在内存中的首地址,也就是它的指针,+1表示偏移到字符数组的第二个位置也就是a[1]开始读入数据
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]=max(f[i-1][j],f[i][j-1]);
}
}
}
cout<<f[n][m];
return 0;
}
最后,诸位不奇怪为什么直接两层for
循环嵌套就能如此顺利的求解吗?
我们最开始的思路可是同时考虑i
和j
的,类似于上帝视角,两根指针分别在两个串上滑动,思考起来相当流畅。但当真正开始写的时候就会发现,这个时候两层for
循环嵌套其实是一根的指针不动,一根滑倒底的那种。。顿时产生一种怪异感。
但是,想到这是线性DP,又是计算n*m个状态,结合f[N][N]数组,我们不难发现,依据表达式,f[N][N]中的每一项或者说状态都是来自——当前层左侧和上一层的(我画的图太丑了就不放了),恰好符合两层for
遍历的顺序。每次更新f[i][j]用到的值都是已经更新过的了。
强烈建议稍微画一下二维表格
,随便挑一个格子,按f[i][j]=f[i-1][j-1]+1;
和 f[i][j]=max(f[i-1][j],f[i][j-1]);
比划一下就知道了,最好再找两个字串按for
循环嵌套的方式更新一下,你就会发现每次需要用到的值都已经更新好了。
总结一下:直接两层for
循环嵌套就能如此顺利的求解,是因为状态转移方程表达式决定的,每次更新一个状态,我们都要确保它所需要用到的状态已经在前面更新过了!
如果发现了上面每一次更新只用到了前面一层和当前层左侧
的数据,那么理所当然的想到用滚动数组来优化了!
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1010;
int n,m;
int f[2][N];
char a[N],b[N];
int main()
{
cin>>n>>m>>(a+1)>>(b+1);
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(a[i]==b[j]) f[i%2][j]=f[(i-1)%2][j-1]+1;
else{
f[i%2][j]=max(f[(i-1)%2][j],f[i%2][j-1]);
}
}
}
cout<<f[n%2][m];
return 0;
}
还有些按位与来实现滚动数组的,肯定比求余要快的:
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(a[i]==b[j]) f[i&1][j]=f[(i-1)&1][j-1]+1;
else{
f[i&1][j]=max(f[(i-1)&1][j],f[i&1][j-1]);
}
}
}
cout<<f[n&1][m];