最长公共子序列(Longest Common Subsequence, LCS)问题是计算机科学中的经典问题,用于寻找两个或多个序列(通常是字符串)中最长的相同子序列,但子序列中的元素不一定在原序列中保持原有的连续性。简单来说,就是找出一个子序列,它既存在于序列A中,又存在于序列B中,且长度最长。LCS问题在文本编辑、生物信息学等领域有广泛应用。
基本概念:
- 子序列:一个序列可以通过删除原序列中某些元素(也可以不删除)得到,但不能改变元素之间的相对顺序。例如,对于序列 "ABCDEF",其子序列包括 "A", "AB", "ABC", "AD", "B", "BC", "C", ..., "DEF", "F" 等。
最长公共子序列(LCS):给定两个序列 X=x1,x2,...,xm 和Y=y1,y2,...,yn,它们的最长公共子序列 Z=z1,z2,...,zk 是满足以下条件的最长子序列:
- Z 是 X 的子序列。
- Z 是 Y 的子序列。
动态规划解法: LCS问题可以使用动态规划方法有效解决。定义一个二维数组 L[i][j] 表示序列X[1..i] 和序列Y[1..j] 的最长公共子序列的长度。状态转移方程如下:
[ L[i][j] = \begin{cases} L[i-1][j-1] + 1, & \text{如果 } x_i = y_j \ \max(L[i-1][j], L[i][j-1]), & \text{如果 } x_i \neq y_j \end{cases} ]
解释:当 xi 与 yj 相等时,最长公共子序列可以通过在前一个状态 L[i−1][j−1] 的基础上增加一个字符得到;否则,最长公共子序列可能来自 X[1..i−1] 与 Y[1..j] 的最长公共子序列,也可能来自 X[1..i] 与 Y[1..j−1] 的最长公共子序列,取两者中的较大值。
初始化状态:[0]=0L[0][j]=L[i][0]=0,表示一个空序列与任何序列的最长公共子序列长度为0。
最终答案即 L[m][n],表示整个序列 X 和序列 Y 的最长公共子序列的长度。
如果需要找出具体的最长公共子序列,可以使用一个额外的二维数组 P[i][j] 存储决策信息,记录 L[i][j] 是通过哪个状态转移得到的。回溯 P 数组即可重构最长公共子序列。
以下是使用动态规划解决最长公共子序列问题的Python示例:
Python
1def longest_common_subsequence(X, Y):
2 m, n = len(X), len(Y)
3 L = [[0] * (n + 1) for _ in range(m + 1)]
4
5 for i in range(1, m + 1):
6 for j in range(1, n + 1):
7 if X[i - 1] == Y[j - 1]:
8 L[i][j] = L[i - 1][j - 1] + 1
9 else:
10 L[i][j] = max(L[i - 1][j], L[i][j - 1])
11
12 return L[m][n]
13
14# 示例
15X = "ABCBDAB"
16Y = "BDCAB"
17
18lcs_length = longest_common_subsequence(X, Y)
19print("最长公共子序列长度:", lcs_length)
定义了一个名为longest_common_subsequence
的函数,它接受两个字符串 X
和 Y
作为参数,返回它们的最长公共子序列的长度。在代码中,动态规划表 L
的大小为 (m+1) x (n+1)
,其中 m
和 n
分别是字符串 X
和 Y
的长度。通过双重循环计算出每一项 L[i][j]
的值,最后返回 L[m][n]
即为所求的最长公共子序列长度。