最长公共子序列问题 (LCS)
给定两个字符串 S
(n) 和 T
(m) ,求出两个字符串的最长公共子序列的长度
! \color{Red}! ! 限制条件
1 <= n, m <= 1000
子序列:
字符串S
的子序列,是一组按从左到右顺序出现的字符,但不一定是连续的
例如 ,我们有一字符串
A
C
T
T
G
C
G
\color{gray}ACTTGCG
ACTTGCG
ACT
,ATTC
,T
这些都是此字符串的子序列TTA
不是此字符串的子序列
公共子序列
例如,我们有字符串 S1
和 S2
S
1
=
A
A
A
C
C
G
T
G
A
G
T
T
A
T
T
C
G
T
T
C
T
A
G
A
A
\color{gray}S1 =AAACCGT GAGT T AT T CGT T CT AGAA
S1=AAACCGTGAGTTATTCGTTCTAGAA
S 2 = C A C C C C T A A G G T A C C T T T G G T T C \color{gray}S2 = CACCCCT AAGGT ACCT T T GGT T C S2=CACCCCTAAGGTACCTTTGGTTC
那么对于 S1
和 S2
来说,它们的 LCS
就是:
S
1
=
A
A
A
C
C
G
T
G
A
G
T
T
A
T
T
C
G
T
T
C
T
A
G
A
A
\color{gray}S1 =\color{gray}AA\color{Red}{ACC}\color{gray}G\color{Red}T\color{gray}G\color{Red}AG\color{gray}T\color{Red} T A\color{gray}T T \color{Red}C\color{gray}G\color{Red}T T \color{gray}C\color{Red}T \color{gray}A\color{Red}G\color{gray}AA
S1=AAACCGTGAGTTATTCGTTCTAGAA
S 2 = C A C C C C T A A G G T A C C T T T G G T T C \color{gray}S2 = C\color{Red}A\color{gray}CC\color{Red}CCT A\color{gray}AG\color{Red}GT AC\color{gray}C\color{Red}T T T G\color{gray}GT T C S2=CACCCCTAAGGTACCTTTGGTTC
L C S = A C C T A G T A C T T T G \color{gray}LCS = \color{Red}ACCTAGTACTTTG LCS=ACCTAGTACTTTG
算法分析 \color{Turquoise}算法分析 算法分析
我们的目的是要找出 字符串 X = < x1,x2,···,xm> 和 Y = < y1 , y2,···,yn > 最长公共子序列
假设 Z = < z1, z2,···,zk > 是 它们的最长公共子序列
-
若 x m = y n x_m = y_n xm=yn ;
- 此时 z k = x m = y n zk = xm = yn zk=xm=yn,我们有 Z k − 1 Z_{k-1} Zk−1 是 X m − 1 与 Y n − 1 X_{m-1} 与 Y_{n-1 } Xm−1与Yn−1的 LCS
-
若 x m ! = y n x_m ! = y_n xm!=yn
- 则 z k ! = x m z_k ! = x_m zk!=xm ,说明 Z k Z_k Zk 是 X m − 1 与 Y n X_{m-1} 与 Y_{n} Xm−1与Yn的 LCS
- 则 z k ! = y n z_k ! = y_n zk!=yn ,说明 Z k Z_k Zk 是 X m 与 Y n − 1 X_{m} 与 Y_{n-1 } Xm与Yn−1的 LCS
因此,求解LCS的问题则变成递归求解两个子问题。由于在过程中,重复的子问题多,效率低下。故此我们可以采用记忆化搜索 - 空间换时间,用数组保存中间状态,方便以后的计算。这是动态规划(DP)的核心思想
DP 求解 LCS
用二维数组
f
[
i
]
[
j
]
f[ i ] [ j ]
f[i][j] 记录字符串
x
1
,
x
2
,
⋅
⋅
⋅
,
x
i
x1,x2,···,xi
x1,x2,⋅⋅⋅,xi 和
y
1
,
y
2
,
⋅
⋅
⋅
,
y
j
y1 , y2,···,yj
y1,y2,⋅⋅⋅,yj 的LCS 长度,则可以得到状态转移方程:
代码实现
const int N = 1010;
int n, m; // 串 S ,T 的长度
char S[N], T[N];
int f[N][N];
void solve()
{
for(int i = 1; i <= n; i ++ )
{
for(int j = 1; j <= m; j ++ )
if(s[i] == T[j])
f[i][j] = f[i - 1][j - 1] + 1;
else
f[i][j] = max(f[i][j - 1], f[i - 1][j]);
}
printf("%d\n", f[n][m]);
}
以上内容尚未完全,随着今后学习的推进,我会继续对其进行补充与完善。
参考资料
[1] cs2035, Longest Common Subsequence.
[2] 最长公共子序列与最长公共子串
[3] GeeksforGeeks, Dynamic Programming | Set 29 (Longest Common Substring).