最长公共子序列,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 的公共子序列可能来自于:
- 若 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
- s 1 . . . s i s_1...s_i s1...si 和 t 1 . . . t i + 1 t_1...t_{i+1} t1...ti+1 的公共子序列
- 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} t0∼t0∼n,以及 s 0 ∼ s 0 ∼ n s_0\sim s_{0\sim n} s0∼s0∼n 和 空串 的最长公共子序列为空
于是可以从初始情况开始不断向后递推,若两串当前位相同,则其最长公共子序列为情况 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...si和t1...tj的最长公共子序列(LCS)长度初始条件:dp[0∼n][0]=0dp[0][0∼m]=0递推关系:dp[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[i−1][j−1]+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[i−1][j],dp[i][j−1]),即用左边和上面格子的值中的较大者更新当前格的值)
#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]:=s的前i项和p的前j项的最短公共父序列的长度,于是有:
-
若其中一个字符串为空,最短公共父序列一定包含另一个字符串的全部字符,即 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,(1≤i≤n,1≤j≤m)
-
若 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[i−1][j−1]+1
-
若 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[i−1][j]+1,dp[i][j−1]+1)
由此就可以求出最短公共父序列的长度。
对于统计这样的父序列的总个数,我们可以另外定义 c o u n t [ i ] [ j ] : = s 的 前 i 项 和 p 的 前 j 项 的 最 短 公 共 父 序 列 的 个 数 count[i][j]:=s的前i项和p的前j项的最短公共父序列的个数 count[i][j]:=s的前i项和p的前j项的最短公共父序列的个数,于是有:
-
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,(1≤i≤n,1≤j≤m)
-
若 s [ i ] = p [ j ] s[i]=p[j] s[i]=p[j],则从 d p [ i − 1 ] [ j − 1 ] dp[i-1][j-1] dp[i−1][j−1] 的情况转移来的父序列最短,于是 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[i−1][j−1]
-
若 s [ i ] ≠ p [ j ] s[i]\neq p[j] s[i]=p[j],再分三种情况:
- 若 d p [ i − 1 ] [ j ] < d p [ i ] [ j − 1 ] dp[i-1][j]<dp[i][j-1] dp[i−1][j]<dp[i][j−1],则说明从 d p [ i − 1 ] [ j ] dp[i-1][j] dp[i−1][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[i−1][j]
- 若 d p [ i − 1 ] [ j ] > d p [ i ] [ j − 1 ] dp[i-1][j]>dp[i][j-1] dp[i−1][j]>dp[i][j−1],则说明从 d p [ i ] [ j − 1 ] dp[i][j-1] dp[i][j−1] 的情况转移来的父序列最短,于是 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][j−1]
- 若 d p [ i − 1 ] [ j ] = d p [ i ] [ j − 1 ] dp[i-1][j]=dp[i][j-1] dp[i−1][j]=dp[i][j−1],则说明从这两种情况转移来的父序列均最短,于是 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[i−1][j]+count[i][j−1]
由此我们也求出了最短父序列的个数。
总结:
{ 定 义 : 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]:=s的前i项和p的前j项的最短公共父序列的长度count[i][j]:=s的前i项和p的前j项的最短公共父序列的个数初始条件:dp[i][0]=i,dp[0][j]=j,(1≤i≤n,1≤j≤m)count[i][0]=1,count[0][j]=1,(1≤i≤n,1≤j≤m)递推关系:dp[i][j]={dp[i−1][j−1]+1,min(dp[i−1][j]+1,dp[i][j−1]+1),s[i]=p[j]s[i]=p[j]count[i][j]=⎩⎪⎪⎪⎨⎪⎪⎪⎧count[i−1][j−1],⎩⎪⎨⎪⎧count[i−1][j],count[i][j−1],coount[i−1][j]+count[i][j−1],dp[i−1][j]<dp[i][j−1]dp[i−1][j]>dp[i−1][j]dp[i−1][j]=dp[i][j−1],s[i]=p[j]s[i]=p[j]