[题解]最长公共子序列

48 篇文章 4 订阅

题目

题目描述

求两个仅包含小写字母的字符串 X , Y X,Y X,Y的最长公共子序列长度。

输入格式

第一行为两个正整数 m , n ( m < 1000 , n < 1000 ) m,n(m < 1000,n < 1000) m,nm<1000,n<1000,分别为两个 X , Y X,Y X,Y两个字符串的长度。

第二行是一个长度为 m m m的只包含小写字母的字符串。

第三行是一个长度为 n n n的只包含小写字母的字符串。

输出格式

根据题目要求输出一个正整数。

题解

#include<iostream>
#include<cmath>
#include<algorithm>
using namespace std;
const int N = 1010;
int f[N][N];
char X[N],Y[N];
int main(){
    int m,n;
    cin >> m >> n;
    cin >> X + 1 >> Y + 1;
    for(int i = 1;i <= m;i++)
        for(int j = 1;j <= n;j++)
            if(X[i] == Y[j]) f[i][j] = f[i - 1][j - 1] + 1;
            else f[i][j] = max(f[i][j - 1],f[i - 1][j]);
    cout << f[m][n] << endl;
    return 0;
}

这道题是动态规划的入门题。

在理解这道题之前请先确定理解了序列是什么。

举个例子:
对于字符串abcdefghabcbcd是他的子序列,acfgdgh同样也是它的子序列。
子序列中的元素在原序列中可以不连续

朴素思路

我们先考虑朴素的思路。
比如对于样例:

abcbdab
bdcaba

其中我们可以分别枚举出两个字符串的子序列,比找出其中最长的一个共有的子序列,这个子序列的长度即为答案。

朴素做法枚举了每一个子序列,所以时间复杂度为 O ( 2 m + n ) O(2 ^{m + n}) O(2m+n)

时间复杂度限制了我们使用朴素思路求解此题。

动态规划思路

状态表示

类似于斐波那契数列这道题,这道题同样用一个数组 f f f存储一些我们需要的状态

f [ i ] [ j ] f[i][j] f[i][j]表示 X X X的前 i i i个字符和 Y Y Y的前 j j j个字符的最长公共子序列长度。

——先不急怀疑这么表示的可行性,我们先假定真的可以这么表示,假定我们这么表示是可以算出答案的。

如果使用这样的状态表示,我们同样可以像朴素做法一样枚举到原字符串的所有子序列的长度,也就是说我们没有漏掉枚举任何一个子序列,而 f [ m ] [ n ] f[m][n] f[m][n] X X X的前 i i i个字符和 Y Y Y的前 j j j个字符的最长子序列长度)便是我们要输出的值了。

状态计算

接下来我们看看 f [ i ] [ j ] f[i][j] f[i][j]究竟可不可以推导出来。

我们先来看一个小的样例,找找规律:

abc
bdc

注意:为了方便,我们下标从1开始,具体原因后面解释

对于这两个字符串,它们的 f [ 3 ] [ 3 ] f[3][3] f[3][3]等于多少呢?
不难看出,它的最长公共子序列为2,所以 f [ 3 ] [ 3 ] = 2 f[3][3] = 2 f[3][3]=2

肉眼是容易看出,但它能不能像类似于斐波那契中的 f [ i ] = f [ i − 1 ] + f [ i − 2 ] f[i] = f[i - 1] + f[i - 2] f[i]=f[i1]+f[i2],从更小的数据规模中推算出来呢?

在这里插入图片描述
如上图所示,我们发现,因为 X [ 3 ] X[3] X[3] Y [ 3 ] Y[3] Y[3]相等。
X X X的前两个字符和 Y Y Y的前两个字符的公共子序列长为1,所以我们可以看成是在前面最长公共子序列的基础上新找到了一个匹配的字符c。因此, f [ 3 ] [ 3 ] = X 前 两 字 符 和 Y 前 两 字 符 的 公 共 子 序 列 最 长 长 度 + 1 f[3][3] = X前两字符和Y前两字符的公共子序列最长长度 + 1 f[3][3]=XY+1

又有我们的状态表示,我们可以得知,
X 前 两 字 符 和 Y 前 两 字 符 的 公 共 子 序 列 最 长 长 度 = f [ 2 ] [ 2 ] X前两字符和Y前两字符的公共子序列最长长度 = f[2][2] XY=f[2][2]
所以 f [ 3 ] [ 3 ] = f [ 2 ] [ 2 ] + 1 f[3][3] = f[2][2] + 1 f[3][3]=f[2][2]+1,当然这里有一个特别的条件,那就是 X [ 3 ] = Y [ 3 ] X[3] = Y[3] X[3]=Y[3]

因此,我们可以进一步得出,
X [ i ] = Y [ j ] X[i] = Y[j] X[i]=Y[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

那么问题来了,如果 X [ i ] X[i] X[i] Y [ j ] Y[j] Y[j]不相等呢?

我们再来看一个新的样例:

abcbd
bdcab

f [ 5 ] [ 5 ] f[5][5] f[5][5]等于多少呢?

在这里插入图片描述
如上图所示,我们发现,此时 X [ 5 ] ≠ Y [ 5 ] X[5] \neq Y[5] X[5]=Y[5],但似乎 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仍然是成立的。

这个新增的“1”是从那里来的呢?
——是 X [ 4 ] = Y [ 5 ] X[4] = Y[5] X[4]=Y[5],也就是说 X X X的前4个字符和 Y Y Y的前5个字符在最新的位置上匹配了,所以得到了新的公共子序列最长长度为3,因此有 f [ 5 ] [ 5 ] = f [ 4 ] [ 5 ] = 3 f[5][5] = f[4][5] = 3 f[5][5]=f[4][5]=3

即在 X [ 5 ] X[5] X[5] Y [ 5 ] Y[5] Y[5]不匹配时,我们在 X X X缺了一个字符的状态中取出最优值作为了当前的最优值——
因为 X [ 5 ] ≠ Y [ 5 ] X[5] \neq Y[5] X[5]=Y[5],所以 X X X的前 5 5 5个字符和 Y Y Y的前 5 5 5个字符的最长公共子序列的最大长度等于 X X X的前 4 4 4个字符和 Y Y Y的前 5 5 5个字符的最长公共子序列的最大长度——有没有觉得怪怪的,好像少了点什么——我们是不是同样得考虑最大长度可能等于 X X X的前 5 5 5个字符和 Y Y Y的前 4 4 4个字符的最长公共子序列的最大长度呀?

综上所述,我们得出 X [ i ] ≠ Y [ j ] X[i] \neq Y[j] X[i]=Y[j]时, f [ i ] [ j ] = m a x ( f [ i − 1 ] [ j ] , f [ i ] [ j − 1 ] ) f[i][j] = max(f[i - 1][j],f[i][j - 1]) f[i][j]=max(f[i1][j],f[i][j1])

综合起来,状态转移方程为:
f [ i ] [ j ] = { f [ i − 1 ] [ j − 1 ] + 1 , X [ i ] = Y [ j ] m a x ( f [ i − 1 ] [ j ] , f [ i ] [ j − 1 ] ) , X [ i ] ≠ Y [ i ] f[i][j] = \left\{\begin{matrix} f[i - 1][j - 1] + 1,X[i] = Y[j]\\ max(f[i - 1][j],f[i][j - 1]),X[i] \neq Y[i] \end{matrix}\right. f[i][j]={f[i1][j1]+1,X[i]=Y[j]max(f[i1][j],f[i][j1]),X[i]=Y[i]

为什么下标从1开始?

  1. 我们注意到最终的状态转移方程中存在 i − 1 i - 1 i1 j − 1 j - 1 j1的下标访问情况,如果下标从0开始,需要特别注意防止越界访问。
  2. 状态表示中, f [ i ] [ j ] f[i][j] f[i][j]表示 X X X的前 i i i个字符和 Y Y Y的前 j j j个字符的最长公共子序列长度。因此 i i i j j j 1 1 1开始更加合适。

原创不易,感谢支持!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wingaso

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值