最长公共子序列问题

最长公共子序列,LCS(Longest Common Subsequence)

问题描述

给定两个串 s 1 s 2 . . . s n s_1s_2...s_n s1s2...sn t 1 t 2 . . . t m t_1t_2...t_m t1t2...tm,求这两个串最长的公共子序列的长度,子序列不要求连续,例如 s 1... s n s1...sn s1...sn 的子序列可以表示为 s i 1 s i 2 . . . s i m ( i 1 < i 2 < . . . < i m ) s_{i_1}s_{i_2}...s_{i_m}(i_1<i_2<...<i_m) si1si2...sim(i1<i2<...<im)

解法

找递推关系:

可以定义 d p [ i ] [ j ] dp[i][j] dp[i][j] 表示 s 1 . . . s i s_1...s_i s1...si t 1 . . . t j t_1...t_j t1...tj 的 LCS 长度

s 1 . . . s i + 1 s_1...s_{i+1} s1...si+1 t 1 . . . t i + 1 t_1...t_{i+1} t1...ti+1公共子序列可能来自于:

  1. s i + 1 = t i + 1 s_{i+1}=t_{i+1} si+1=ti+1,在 s 1 . . . s i s_1...s_i s1...si t 1 . . . t i t_1...t_i t1...ti 的公共子序列末尾追加上 s i + 1 s_{i+1} si+1
  2. s 1 . . . s i s_1...s_i s1...si t 1 . . . t i + 1 t_1...t_{i+1} t1...ti+1 的公共子序列
  3. s 1 . . . s i + 1 s_1...s_{i+1} s1...si+1 t 1 . . . t i t_1...t_i t1...ti 的公共子序列

而已知的初始条件是 空串 和 t 0 ∼ t 0 ∼ n t_0\sim t_{0\sim n} t0t0n,以及 s 0 ∼ s 0 ∼ n s_0\sim s_{0\sim n} s0s0n 和 空串 的最长公共子序列为空

于是可以从初始情况开始不断向后递推,若两串当前位相同,则其最长公共子序列为情况 1,否则其最长公共子序列为情况 2 和 3 中子序列较长的那一种(情况 1 得到的结果不会坏与情况 2 和情况 3,因为使两个字符串中的其中一个字符串增加一个字符,最多使这两个字符串的最长公共子序列长度增加 1,即 d p [ i + 1 ] [ j ] ≤ d p [ i ] [ j ] + 1 , d p [ i ] [ j + 1 ] ≤ d p [ i ] [ j ] + 1 dp[i+1][j]\le dp[i][j]+1,dp[i][j+1]\le dp[i][j]+1 dp[i+1][j]dp[i][j]+1,dp[i][j+1]dp[i][j]+1 )

d p [ i + 1 ] [ j + 1 ] = { d p [ i ] [ j ] + 1 , s i + 1 = t j + 1 m a x ( d p [ i ] [ j + 1 ] , d p [ i + 1 ] [ j ] ) , s i + 1 ≠ t j + 1 dp[i+1][j+1]= \begin{cases} dp[i][j]+1, &s_{i+1}=t_{j+1} \\ max(dp[i][j+1],dp[i+1][j]), &s_{i+1}\neq t_{j+1} \end{cases} dp[i+1][j+1]={dp[i][j]+1,max(dp[i][j+1],dp[i+1][j]),si+1=tj+1si+1=tj+1

{ 定 义 : d p [ i ] [ j ] : = s 1 . . . s i 和 t 1 . . . t j 的 最 长 公 共 子 序 列 ( L C S ) 长 度 初 始 条 件 : d p [ 0 ∼ n ] [ 0 ] = 0 d p [ 0 ] [ 0 ∼ m ] = 0 递 推 关 系 : d p [ i + 1 ] [ j + 1 ] = { d p [ i ] [ j ] , s i + 1 = t j + 1 m a x ( d p [ i ] [ j + 1 ] , d p [ i + 1 ] [ j ] ) , s i + 1 ≠ t j + 1 \begin{aligned} \begin{cases} & 定义: \\ & dp[i][j]:=s_1...s_i 和 t_1...t_j 的 最长公共子序列(LCS) 长度 \\ \\ & 初始条件: \\ & dp[0\sim n][0]=0 \\ & dp[0][0\sim m]=0 \\ \\ & 递推关系: \\ & dp[i+1][j+1]= \begin{cases} dp[i][j], &s_{i+1}=t_{j+1} \\ max(dp[i][j+1],dp[i+1][j]), &s_{i+1}\neq t_{j+1} \\ \end{cases} \end{cases} \end{aligned} dp[i][j]:=s1...sit1...tj(LCS)dp[0n][0]=0dp[0][0m]=0dp[i+1][j+1]={dp[i][j],max(dp[i][j+1],dp[i+1][j]),si+1=tj+1si+1=tj+1

经过 O ( n × m ) O(n\times m) O(n×m) 的递推之后可得 s 和 t 两串的最长公共子序列长度

(递推过程用图画出来更容易理解:当前格(i,j)要计算的值为 d p [ i ] [ j ] dp[i][j] dp[i][j],若有 s i = t j s_i=t_j si=tj,则当前格的值为 d p [ i − 1 ] [ j − 1 ] + 1 dp[i-1][j-1]+1 dp[i1][j1]+1,即用左上方格子的值更新当前格的值,否则当前格的值为 m a x ( d p [ i − 1 ] [ j ] , d p [ i ] [ j − 1 ] ) max(dp[i-1][j],dp[i][j-1]) max(dp[i1][j],dp[i][j1]),即用左边和上面格子的值中的较大者更新当前格的值)

#include <iostream>
using namespace std;
const int maxn = 1005;
int dp[maxn][maxn]; //dp[i][j]表示s1...si和t1...tj对应的LCS长度
char s[maxn], t[maxn];
int n, m;
void solve()
{
    for (int i = 0; i < n; i++)
    {
        for (int j = 0; j < m; j++)
        {
            if (s[i] == t[i]) //因为dp[i][j]+1>=dp[i+1][j]和dp[i][j+1],所以若s[i]==t[i]可以直接取dp[i][j]+1
                dp[i + 1][j + 1] = dp[i][j] + 1;
            else //否则取dp[i][j+1]和dp[i+1][j]中的较大值
                dp[i + 1][j + 1] = max(dp[i][j + 1], dp[i + 1][j]);
        }
    }
    printf("%d\n", dp[n][m]); //s1...sn和t1...tm的最长公共子序列的长度
}

扩展:最短公共父序列

题意:

给定两个字符串 s s s p p p,求同时包含这两个字符串作为子序列的最短字符串,输出最短字符串的长度以及最多能够有多少个这样的字符串。

思路:

这个题目与最长公共子序列相似,我们可以定义 d p [ i ] [ j ] : = s 的 前 i 项 和 p 的 前 j 项 的 最 短 公 共 父 序 列 的 长 度 dp[i][j]:=s的前i项和p的前j项的最短公共父序列的长度 dp[i][j]:=sipj,于是有:

  1. 若其中一个字符串为空,最短公共父序列一定包含另一个字符串的全部字符,即 d p [ i ] [ 0 ] = i , d p [ 0 ] [ j ] = j , ( 1 ≤ i ≤ n , 1 ≤ j ≤ m ) dp[i][0]=i,dp[0][j]=j,(1\le i\le n,1\le j\le m) dp[i][0]=i,dp[0][j]=j,(1in,1jm)

  2. s [ i ] = p [ j ] s[i]=p[j] s[i]=p[j],则当前字符只需被考虑一次, d p [ i ] [ j ] = d p [ i − 1 ] [ j − 1 ] + 1 dp[i][j]=dp[i-1][j-1]+1 dp[i][j]=dp[i1][j1]+1

  3. s [ i ] ≠ p [ j ] s[i]\neq p[j] s[i]=p[j],则 d p [ i ] [ j ] = m i n ( d p [ i − 1 ] [ j ] + 1 , d p [ i ] [ j − 1 ] + 1 ) dp[i][j]=min(dp[i-1][j]+1,dp[i][j-1]+1) dp[i][j]=min(dp[i1][j]+1,dp[i][j1]+1)

由此就可以求出最短公共父序列的长度。

对于统计这样的父序列的总个数,我们可以另外定义 c o u n t [ i ] [ j ] : = s 的 前 i 项 和 p 的 前 j 项 的 最 短 公 共 父 序 列 的 个 数 count[i][j]:=s的前i项和p的前j项的最短公共父序列的个数 count[i][j]:=sipj,于是有:

  1. c o u n t [ i ] [ 0 ] = 1 , c o u n t [ 0 ] [ j ] = 1 , ( 1 ≤ i ≤ n , 1 ≤ j ≤ m ) count[i][0]=1,count[0][j]=1,(1\le i\le n,1\le j\le m) count[i][0]=1,count[0][j]=1,(1in,1jm)

  2. s [ i ] = p [ j ] s[i]=p[j] s[i]=p[j],则从 d p [ i − 1 ] [ j − 1 ] dp[i-1][j-1] dp[i1][j1] 的情况转移来的父序列最短,于是 c o u n t [ i ] [ j ] = c o u n t [ i − 1 ] [ j − 1 ] count[i][j]=count[i-1][j-1] count[i][j]=count[i1][j1]

  3. s [ i ] ≠ p [ j ] s[i]\neq p[j] s[i]=p[j],再分三种情况:

    1. d p [ i − 1 ] [ j ] < d p [ i ] [ j − 1 ] dp[i-1][j]<dp[i][j-1] dp[i1][j]<dp[i][j1],则说明从 d p [ i − 1 ] [ j ] dp[i-1][j] dp[i1][j] 的情况转移来的父序列最短,于是 c o u n t [ i ] [ j ] = c o u n t [ i − 1 ] [ j ] count[i][j]=count[i-1][j] count[i][j]=count[i1][j]
    2. d p [ i − 1 ] [ j ] > d p [ i ] [ j − 1 ] dp[i-1][j]>dp[i][j-1] dp[i1][j]>dp[i][j1],则说明从 d p [ i ] [ j − 1 ] dp[i][j-1] dp[i][j1] 的情况转移来的父序列最短,于是 c o u n t [ i ] [ j ] = c o u n t [ i ] [ j − 1 ] count[i][j]=count[i][j-1] count[i][j]=count[i][j1]
    3. d p [ i − 1 ] [ j ] = d p [ i ] [ j − 1 ] dp[i-1][j]=dp[i][j-1] dp[i1][j]=dp[i][j1],则说明从这两种情况转移来的父序列均最短,于是 c o u n t [ i ] [ j ] = c o u n t [ i − 1 ] [ j ] + c o u n t [ i ] [ j − 1 ] count[i][j]=count[i-1][j]+count[i][j-1] count[i][j]=count[i1][j]+count[i][j1]

由此我们也求出了最短父序列的个数。

总结:

{ 定 义 : d p [ i ] [ j ] : = s 的 前 i 项 和 p 的 前 j 项 的 最 短 公 共 父 序 列 的 长 度 c o u n t [ i ] [ j ] : = s 的 前 i 项 和 p 的 前 j 项 的 最 短 公 共 父 序 列 的 个 数 初 始 条 件 : d p [ i ] [ 0 ] = i , d p [ 0 ] [ j ] = j , ( 1 ≤ i ≤ n , 1 ≤ j ≤ m ) c o u n t [ i ] [ 0 ] = 1 , c o u n t [ 0 ] [ j ] = 1 , ( 1 ≤ i ≤ n , 1 ≤ j ≤ m ) 递 推 关 系 : d p [ i ] [ j ] = { d p [ i − 1 ] [ j − 1 ] + 1 , s [ i ] = p [ j ] m i n ( d p [ i − 1 ] [ j ] + 1 , d p [ i ] [ j − 1 ] + 1 ) , s [ i ] ≠ p [ j ] c o u n t [ i ] [ j ] = { c o u n t [ i − 1 ] [ j − 1 ] , s [ i ] = p [ j ] { c o u n t [ i − 1 ] [ j ] , d p [ i − 1 ] [ j ] < d p [ i ] [ j − 1 ] c o u n t [ i ] [ j − 1 ] , d p [ i − 1 ] [ j ] > d p [ i − 1 ] [ j ] c o o u n t [ i − 1 ] [ j ] + c o u n t [ i ] [ j − 1 ] , d p [ i − 1 ] [ j ] = d p [ i ] [ j − 1 ] , s [ i ] ≠ p [ j ] \begin{aligned} \begin{cases} & 定义: \\ & dp[i][j]:=s的前i项和p的前j项的最短公共父序列的长度 \\ & count[i][j]:=s的前i项和p的前j项的最短公共父序列的个数 \\ \\ & 初始条件: \\ & dp[i][0]=i,dp[0][j]=j,(1\le i\le n,1\le j\le m) \\ & count[i][0]=1,count[0][j]=1,(1\le i\le n,1\le j\le m) \\ \\ & 递推关系: \\ & dp[i][j]= \begin{cases} dp[i-1][j-1]+1, &s[i]=p[j] \\ min(dp[i-1][j]+1,dp[i][j-1]+1), &s[i]\neq p[j] \\ \end{cases}\\ \\ & count[i][j]= \begin{cases} count[i-1][j-1], &s[i]=p[j] \\ \begin{cases} count[i-1][j], &dp[i-1][j]<dp[i][j-1]\\ count[i][j-1], &dp[i-1][j]>dp[i-1][j]\\ coount[i-1][j]+count[i][j-1], &dp[i-1][j]=dp[i][j-1] \end{cases}, &s[i]\neq p[j] \\ \end{cases} \\ \end{cases} \end{aligned} dp[i][j]:=sipjcount[i][j]:=sipjdp[i][0]=i,dp[0][j]=j,(1in,1jm)count[i][0]=1,count[0][j]=1,(1in,1jm)dp[i][j]={dp[i1][j1]+1,min(dp[i1][j]+1,dp[i][j1]+1),s[i]=p[j]s[i]=p[j]count[i][j]=count[i1][j1],count[i1][j],count[i][j1],coount[i1][j]+count[i][j1],dp[i1][j]<dp[i][j1]dp[i1][j]>dp[i1][j]dp[i1][j]=dp[i][j1],s[i]=p[j]s[i]=p[j]

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值