【线性dp】| AcWing 算法基础班试题总结(二)

897. 最长公共子序列

题目描述

给定两个长度分别为N和M的字符串A和B,求既是A的子序列又是B的子序列的字符串长度最长是多少。

输入格式
第一行包含两个整数N和M。
第二行包含一个长度为N的字符串,表示字符串A。
第三行包含一个长度为M的字符串,表示字符串B。

字符串均由小写字母构成。

输出格式
输出一个整数,表示最大长度。

数据范围
1≤N,M≤1000

输入样例:

4 5
acbd
abedc

输出样例:

3

代码实现

思路一

集合表示f[ i,j ] 表示 a 的前 i 个字符,和 b 的前 j 个字符的最长公共子序列长度。(两个前缀子串的最长公共子序列)

集合划分:以a[i]、b[j]是否包含在子序列当中为依据,因此可以分成四类:

扫描二维码关注公众号,回复: 12871331 查看本文章

a[i]不在,b[j]不在f [i−1,j−1]

a[i] 不在,b[j]在f[i−1,j]

看似是f[i−1,j] , 实际上无法用f[i−1,j]表示。因为f[i−1,j]表示的是在a的前i-1个字母和b的前j个字母的最长公共子序列长度,但是这个满足条件f[i-1,j]的最大子序列却不一定包括b[j],而我们要求的是a[i]不在子序列中且b[j]在子序列中的情况,求它的长度。这二者不完全相等。

但仍可以用f[i−1,j]来表示,原因就在于a[i]不在子序列中且b[j]在子序列中的情况被包含在f[i−1,j]中,即该情况是 f[i−1,j]情况的子集,其最大值小于等于f[i -1,j]的最大值,另一个子集是a[i]不在且b[j]也不在子序列中的情况f [i−1,j−1],二者合并起来就是全集f[i-1,j]

虽然f[i - 1,j]的最大值不一定是 子序列包含b[j] 这种情况,但对答案是没影响的,因为f[i-1,j]中的方案一定是f[i,j]中的方案,只要包含了选b[j]这种情况就行。

f [ i − 1 , j ] 的 值 可 以 分 为 两 部 分 构 成 : 不 选 b [ j ] 和 选 b [ j ] , 就 是 m a x ( f [ i − 1 , j − 1 ] , 选 b [ j ] 的 情 况 ) f[i-1,j]的值可以分为两部分构成:不选b[j]和选b[j],就是 max(f[i-1,j-1],选b[j]的情况) f[i1j]b[j]b[j]max(f[i1j1]b[j])

例如:要求a,b,c的最大值可以这样求:max( max(a,b) , max(b,c) ) 虽然b被重复使用,但仍能求出max,求max只要保证不漏即可。

a[i]在,b[j]不在f[i,j-1],原理同②

a[i]在,b[j]在f[i−1,j−1]+1; 前提条件:a[i]==b[j]

因为在计算时,①已经被包含在②和③的情况中,所以①不用考虑了。且情况②和情况③是必然发生的。

即,对于二维表f,每次求f[i,j]时,最多用到三个方向,左f[i-1,j],上f[i,j-1]和左上方f[i-1,j-1],左和上必然会用到,左上方用到需要有前提条件:a[i]==b[j]

初始化情况:

由于涉及i-1、j-1,所以f数组最好从下标1开始存储,因此相关的字符串也从下标1开始存储比较好。

从第1个字符开始处理,所以需要先预处理’第0个字符‘的情况:第0行和第0列都应该初始化为0

即任何字符串和null字符串匹配都是0。

i=1时,要用到每一列的f[0,j];j=1时,要用到每一行的f[i,0];

i=1、j=1且a[i]=b[j]时,要用到f[0,0]

#include <iostream>
#define read(x) scanf("%d",&x)

using namespace std;

const int N=1010;
char a[N],b[N];
int f[N][N];

int main()
{
    
    
    int n,m;
    read(n),read(m);
    scanf("%s%s",a+1,b+1);
    
    for (int i=1;i<=n;i++)
        for (int j=1;j<=m;j++) {
    
    
            f[i][j]=max(f[i-1][j],f[i][j-1]);
            if (a[i]==b[j])  f[i][j]=max(f[i][j],f[i-1][j-1]+1);
        }
    
    printf("%d",f[n][m]);
    
    return 0;
}

由于求完f[i,j]后,它还会被后面的用到,作为左、上、左上方三个方向的方向被用三次,所以不太可能仅用4个变量代表左、上、左上和答案去优化空间复杂度。

思路二

思路2和思路1看着差不多,但是实际思考方式还是不太一样,且代码的含义也不一样。

思考一个问题:当a[i]==b[j]时,我们是一定要把他们匹配,作为公共子序列的一部分吗?

下面的思路都是在此条件成立的基础上进行的。

按两个序列末尾的字符是不是相等来区分。
如果两个字符相等,就可以直接转移到f[i-1,j-1];
不相等的话,两个字符一定有一个可以抛弃,可以对f[i-1,j],f[i,j-1]两种状态取max来转移。

#include <iostream>

using namespace std;

const int N = 1010;
char a[N], b[N];
int f[N][N];

int main() {
    
    
  int n, m;
  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][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;
}

猜你喜欢

转载自blog.csdn.net/HangHug_L/article/details/114409928