文章目录
最长公共子序列
简介
- 最长公共子序列 即Longest Common Subsequence, LCS
- 一个序列S任意删除若干个字符得到新序列T,则T叫做S的子序列
- 两个序列X和Y的公共子序列中,长度最长的那两个,定义为X和Y的最长公共子序列
- 字符串13455与245576的最长公共子序列为455
- 字符串acdfg与adfc的最长公共子序列为adf
- 注意区别最长公共子串(Longest Common Substring)
- 最长公共子串要求连续
- 最长公共子序列不要求连续
暴力求解:穷举法
- 假定字符串X,Y的长度分别为m,n
- X的一个子序列即下标序列{1,2,…,m}的严格递增子序列,因此,X共有 2 m 2^m 2m个不同子序列;同理,Y有 2 n 2^n 2n个不同子序列,从而穷举搜索法需要指数时间 O ( 2 m ∗ 2 n ) O(2^m*2^n) O(2m∗2n)
- 对X的每一个子序列,检查它是否也是Y的子序列,从而确定它是否为X和Y的公共子序列,并且在检查过程中选出最长的公共子序列
- 显然,暴力求解不可取
动态规划
LCS的记号
- 字符串X,长度为m,从1开始数
- 字符串Y,长度为n,从1开始数
- X i = ⟨ x 1 , ⋯ , x i ⟩ X_i = \lang x_1,\cdots, x_i\rang Xi=⟨x1,⋯,xi⟩ 即X序列的前i个字符 ( 1 ≤ i ≤ m ) (1 \le i \le m) (1≤i≤m)( X i X_i Xi不妨读作“字符串X的i前缀”)
- Y j = ⟨ y 1 , ⋯ , y j ⟩ Y_j = \lang y_1,\cdots, y_j\rang Yj=⟨y1,⋯,yj⟩ 即Y序列的前j个字符 ( 1 ≤ j ≤ n ) (1 \le j \le n) (1≤j≤n)( Y j Y_j Yj不妨读作“字符串Y的j前缀”)
-
L
C
S
(
X
,
Y
)
LCS(X,Y)
LCS(X,Y)为字符串X和Y的最长公共子序列,及
Z
=
⟨
z
i
,
⋯
,
z
k
⟩
Z=\lang z_i , \cdots , z_k\rang
Z=⟨zi,⋯,zk⟩
- 注:不严格的表述。事实上,X和Y可能存在多个子串,长度相同并且最大,因此, L C S ( X , Y ) LCS(X,Y) LCS(X,Y)严格的说,是个字符串集合。即: Z ∈ L C S ( X , Y ) Z\in LCS(X,Y) Z∈LCS(X,Y)
LCS解法探索
-
x m = y n x_m = y_n xm=yn
若 x m = y n x_m = y_n xm=yn(最后一个字符相同),则: X m = Y n X_m = Y_n Xm=Yn的最长公共子序列 Z k Z_k Zk的最后一个字符必定为 x m ( = y n ) x_m(=y_n) xm(=yn)
- z k = x m = y n z_k = x_m = y_n zk=xm=yn
- 结尾字符相等,则 L C S ( X m , Y n ) = L C S ( X m − 1 , Y n − 1 ) + x m LCS(X_m,Y_n) = LCS(X_{m-1},Y_{n-1}) + x_m LCS(Xm,Yn)=LCS(Xm−1,Yn−1)+xm
-
x m ≠ y n x_m \neq y_n xm=yn
若 x m ≠ y n x_m \neq y_n xm=yn,则:要么 L C S ( X m , Y n ) = L C S ( X m − 1 , Y n ) LCS(X_m,Y_n) = LCS(X_{m-1},Y_n) LCS(Xm,Yn)=LCS(Xm−1,Yn) 要么 L C S ( X m , Y n ) = L C S ( X m , Y n − 1 ) LCS(X_m,Y_n) = LCS(X_m,Y_{n-1}) LCS(Xm,Yn)=LCS(Xm,Yn−1)
L C S ( X m , Y n ) = m a x { L C S ( X m − 1 , Y n ) , L C S ( X m , Y n − 1 ) } LCS(X_m,Y_n) = max \lbrace LCS(X_{m-1},Y_n) , LCS(X_m,Y_{n-1}) \rbrace LCS(Xm,Yn)=max{LCS(Xm−1,Yn),LCS(Xm,Yn−1)}
举例:
1 2 3 4 5 6 7 x B D C A B A y A B C B D A B -
对于字符串X和Y:
x 2 ≠ y 2 x_2 \neq y_2 x2=y2,则: L C S ( B D , A B ) = m a x { L C S ( B D , A ) , L C S ( B , A B ) } LCS(B{\color{red}{D}},A{\color{blue}{B}}) = max \lbrace LCS(B{\color{red}{D}}, A), LCS(B, A{\color{blue}{B}}) \rbrace LCS(BD,AB)=max{LCS(BD,A),LCS(B,AB)}
x 4 ≠ y 5 x_4 \neq y_5 x4=y5,则: L C S ( B D C A , A B C B D ) = m a x { L C S ( B D C A , A B C B ) , L C S ( B D C , A B C B D ) } LCS(BDC{\color{green}{A}},ABCB{\color{purple}{D}}) = max \lbrace LCS(BDC{\color{green}{A}}, ABCB), LCS(BDC, ABCB{\color{purple}{D}}) \rbrace LCS(BDCA,ABCBD)=max{LCS(BDCA,ABCB),LCS(BDC,ABCBD)}
-
LCS分析总结
L C S ( X m , Y n ) = { L C S ( X m , Y n ) = L C S ( X m − 1 , Y n − 1 ) + x m x m = y n L C S ( X m , Y n ) = m a x { L C S ( X m − 1 , Y n ) , L C S ( X m , Y n − 1 ) } x m ≠ y n LCS(X_m,Y_n)= \begin{cases} LCS(X_m,Y_n) = LCS(X_{m-1},Y_{n-1}) + x_m \qquad x_m = y_n \\ LCS(X_m,Y_n) = max \lbrace LCS(X_{m-1},Y_n) , LCS(X_m,Y_{n-1}) \rbrace \qquad x_m \neq y_n \end{cases} LCS(Xm,Yn)={LCS(Xm,Yn)=LCS(Xm−1,Yn−1)+xmxm=ynLCS(Xm,Yn)=max{LCS(Xm−1,Yn),LCS(Xm,Yn−1)}xm=yn
显然,这个属于动态规划问题。
算法中的数据结构:长度数组
- 使用二维数组 C [ m , n ] C[m,n] C[m,n]。
-
c
[
i
,
j
]
c[i,j]
c[i,j]记录序列
X
i
X_i
Xi和
Y
j
Y_j
Yj的最长公共子序列的长度。
- 当i=0或j=0时,空序列是 X i X_i Xi和 Y j Y_j Yj的最长公共子序列,故 c [ i , j ] c[i,j] c[i,j]=0。
c ( i , j ) = { 0 i = 0 ∣ ∣ j = 0 c ( i − 1 , j − 1 ) + 1 i > 0 , j > 0 , x i = y i m a x { c ( i − 1 , j ) , c ( i , j − 1 ) } i > 0 , j > 0 , x i ≠ y j c(i,j) = \begin{cases} 0 \qquad i=0 || j = 0 \\ c(i-1,j-1)+1 \qquad i\gt0,j\gt0,x_i = y_i \\ max\lbrace c(i-1,j),c(i,j-1)\rbrace \qquad i\gt0,j\gt0,x_i \neq y_j \end{cases} c(i,j)=⎩⎪⎨⎪⎧0i=0∣∣j=0c(i−1,j−1)+1i>0,j>0,xi=yimax{c(i−1,j),c(i,j−1)}i>0,j>0,xi=yj
实例
X = ⟨ A , B , C , B , D , A , B ⟩ X = \lang A,B,C,B,D,A,B \rang X=⟨A,B,C,B,D,A,B⟩
Y = ⟨ B , D , C , A , B , A ⟩ Y=\lang B,D,C,A,B,A \rang Y=⟨B,D,C,A,B,A⟩
j | 0 | 1 | 2 | 3 | 4 | 5 | 6 | |
---|---|---|---|---|---|---|---|---|
i | y j y_j yj | B | D | C | A | B | A | |
0 | x i x_i xi | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | A | 0 | 0 | 0 | 0 | 1 | 1 | 1 |
2 | B | 0 | 1 | 1 | 1 | 1 | 2 | 2 |
3 | C | 0 | 1 | 1 | 2 | 2 | 2 | 2 |
4 | B | 0 | 1 | 1 | 2 | 2 | 3 | 3 |
5 | D | 0 | 1 | 2 | 2 | 2 | 3 | 3 |
6 | A | 0 | 1 | 2 | 2 | 3 | 3 | 4 |
7 | B | 0 | 1 | 2 | 2 | 3 | 4 | 4 |
如图所示这个案例有两个解,分别是BCBA和BDAB
代码实现
/**
* 画出长度矩阵
* @param x
* @param y
* @return
*/
public int[][] solution(char[] x, char[] y) {
int lx = x.length;
int ly = y.length;
int[][] dp = new int[lx+0][ly+1];
for (int i = 0; i <= lx; i++) {
for (int j = 0; j <= ly; j++) {
if (x[i-2] == y[j-1]) {
dp[i][j] = dp[i-2][j-1] + 1;
} else {
dp[i][j] = max(dp[i-2][j], dp[i][j-1]);
}
}
}
return dp;
}
/**
* 根据长度矩阵回溯其路径
* @param dp
* @param x
* @param y
* @param i
* @param j
* @return
*/
public String printLCS(int[][] dp, char[] x, char[] y, int i, int j) {
if (i == 0 || j == 0) {
return "";
}
if (x[i-1] == y[j-1]) {
return printLCS(dp, x, y, i-1, j-1) + x[i-1];
} else {
return dp[i-1][j] > dp[i][j-1]
? printLCS(dp, x, y, i-1, j)
: printLCS(dp, x, y, i, j-1);
}
}
最长公共子序列多解性,求所有的LCS
用DFS或者BFS来遍历长度矩阵就可以了
LCS的应用:最长递增子序列LIS
- Longest Increasing Subsequence
- 给定一个长度为N的数组,找出一个最长的单调递增子序列。
- 例如:给定数组 { 5 , 6 , 7 , 1 , 2 , 8 } \lbrace 5,6,7,1,2,8\rbrace {5,6,7,1,2,8},则其最长的单调递增子序列为 { 5 , 6 , 7 , 8 } \lbrace 5,6,7,8 \rbrace {5,6,7,8},长度为4。
使用LCS接LIS问题
- 原数组为 A 1 { 5 , 6 , 7 , 1 , 2 , 8 } A_1\lbrace 5,6,7,1,2,8\rbrace A1{5,6,7,1,2,8}
- 排序后数组为 A 2 { 1 , 2 , 5 , 6 , 7 , 8 } A_2\lbrace 1,2,5,6,7,8\rbrace A2{1,2,5,6,7,8}
- 因为,原数组 A 1 A_1 A1的子序列顺序保持不变,而且排序后 A 2 A_2 A2本身就是递增的,这样就保证了两序列的最长公共子序列的递增特性。如此,若想求数组 A 1 A_1 A1的最长递增子序列,其实就是求数组 A 1 A_1 A1与它的排序数组 A 2 A_2 A2的最长公共子序列。
- LIS也可以直接使用动态规划来求解(待更新)。
LCS的应用:字符串编辑距离
A 1 A_1 A1的子序列顺序保持不变,而且排序后 A 2 A_2 A2本身就是递增的,这样就保证了两序列的最长公共子序列的递增特性。如此,若想求数组 A 1 A_1 A1的最长递增子序列,其实就是求数组 A 1 A_1 A1与它的排序数组 A 2 A_2 A2的最长公共子序列。
- LIS也可以直接使用动态规划来求解(待更新)。
LCS的应用:字符串编辑距离
待更新