问题描述:一个序列 S ,如果分别是两个或多个已知序列的子序列,且是所有符合此条件序列中最长的,则 S 称为已知序列的最长公共子序列。而最长公共子串(要求连续)和最长公共子序列是不同的。
LCS的最优子结构
设 X={x1, x2, x3, x4,..., xm} 和 Y={y1, y2, y3, y4,..., yn} 为两个序列,并设 Z={z1, z2, z3, z4,..., zk} 为 X 与 Y 的任意一个LCS,则:
- 若xm=yn,则zk=xm=yn 且Zk-1是Xm-1和Yn-1的一个LCS
- 若xm≠yn且zk≠xm ,则Z是Xm-1和Y的一个LCS
- 若xm≠yn且zk≠yn ,则Z是X和Yn-1的一个LCS
重叠子问题
由LCS问题的最优子结构可得递归式
满足了动态规划的两个条件之后,该问题可以采用备忘录(也叫打表)思想进行求解,下面举例说明实际操作过程:
设有两个序列x="ABCBDAB"(m=7)和y="BDCABA"(n=6)
步骤一:打表,按行填充二维数组,填充规则参照根据最优子结构得出的递归式(其中第0行第0列作为哨兵可全部填0),填充结果如下
步骤二:从数组的最右下角开始,每遇到朝左上的箭头,则输出字符,最后可得LCS={BCBA}
代码实现如下:
public class Solution {
enum Direction {
L, U, UL, DEFAULT
};
Item NULL = new Item(0, Direction.DEFAULT);
public String LCS(String x, String y) {
//需要第0列作哨兵,所以需要长度+1
int lenx = x.length() + 1, leny = y.length() + 1;
Item c[][] = new Item[lenx][leny];
for (int i = 0; i < lenx; i++) {
c[i][0] = NULL;
}
for (int j = 0; j < leny; j++) {
c[0][j] = NULL;
}
for (int i = 1; i < lenx; i++) {
for (int j = 1; j < leny; j++) {
if (x.charAt(i - 1) == y.charAt(j - 1)) {
c[i][j] = new Item(c[i - 1][j - 1].val + 1, Direction.UL);
} else {
if (c[i][j - 1].val > c[i - 1][j].val) {
c[i][j] = new Item(c[i][j - 1].val, Direction.L);
} else {
c[i][j] = new Item(c[i - 1][j].val, Direction.U);
}
}
}
}
StringBuilder result = new StringBuilder(c[lenx - 1][leny - 1].val);
for (int i = lenx - 1; i >= 0;) {
for (int j = leny - 1; j >= 0;) {
switch (c[i][j].direct) {
case L:
j -= 1;
break;
case U:
i -= 1;
break;
case UL:
result.insert(0, x.charAt(i - 1));
i -= 1;
j -= 1;
break;
case DEFAULT:
default:
return result.toString();
}
}
}
return result.toString();
}
class Item {
int val;
Direction direct;
Item(int v, Direction d) {
this.val = v;
this.direct = d;
}
}
}
另外,需要说明一点,两个字符串的LCS可能存在多个,那么允许lcs(x,y)和lcs(y,x)返回的结果不一样,只需要保证长度相等即可