首先说一下最长公共子序列和最长公共子串的区别。公共最长子序列是可以对你要比较的字符串进行一个或多个的字符删除后所留下的串,不要求一定连续。而最长公共子串要求结果的串一定是连续的。
例如:String s1=”ABCD”;
Stirng s2=”BCAD”;
所以最长公共子序列是BCD.而最长公共子串是BC.
下面介绍算法的具体实现。首先,我们构建一个二维数组dp[][]用来存放最长公共子串的值。仍然以上面的S1,S2为例。dp[][]数组的大小为:行数为S1的长度,列为S2的长度。所以我们就构建了一个4*4的数组。dp[i][j]的含义是s1[0…i]和s2[0….j]的最长公共子序列的长度。
B C A D
A
B
C
D
首先,我们看竖着的这列的第一个字符A,也就是S1[0],用它和横着的BCAD的第一个字符开始比较,若相等,就令dp[0][0]=1,并且剩下的dp[0][0…j]都为1。因为一个字符和BCAD进行比较,求公共最长子序列,那么最大的值最多为1。并且,前面的若为1,后面的字符哪怕是不相等,但是至少也会有1(即前面相等的1);若不等,令dp[0][0]=0;接下来,继续用A比较第二个字符即A和C,不等设dp[0][1]为0和前面的值比较所得的较大值,相等设为1.这样我们就得到dp[][]的第0行为 0 0 1 1;同理我们用s2[0]和s1[0..i]比较,我们得到第0列为:
0
1
1
1
所以dp[][]的第一行第一列为 :0 0 1 1
1
1
1
那么我们继续看dp[][]剩下的位置,其位置的值只能来自下面的三种情况。①来自于dp[i-1][j] 。例如:s1=”A1BC2” s2=”AB34C” dp[3][4]即s1[0..3]和s2[0..4] 他们的最长公共子序列为ABC 所以dp[3][4]=3。dp[4][4]即求s1[0..4]和s2[0..4] 他们的最长公共子序列,也为ABC,所以dp[4][4]也为3.也就是证明了dp[4][4]可能来自于dp[3][4]。
②来自于dp[i][j-1]。例如S1=”A1B2C” s2=”AB3C4” dp[4][3]即S1[0..4]和S2[0..3] 他们的最长公共子序列为“ABC”所以dp[4][3]=3。dp[4][4]也等于3. 也就是证明了dp[4][4]可能来自于dp[4][3]。
③如果S1[i]==S2[j],还可能是dp[i-1][j-1]+1;例如:1=”ABCD” S2=”ABCD” dp[2][2]为3.dp[3][3]为4,即dp[2][2]+1;
综上dp[i][j]取以上三种情况值为最大的那种。所以完整的dp数组如下:
B C A D
A00 1 1
B 1 1 1 1
C 1 2 2 2
D 1 2 2 3
dp[][]矩阵最右下角的值为最长公共子序列的长度。此题为3.求解dp[][]j矩阵代码如下:
public static int[][] finddp(String s1, String s2) {
char[] a = s1.toCharArray();
char[] b = s2.toCharArray();
int[][] dp = new int[a.length][b.length];
dp[0][0] = a[0] == b[0] ? 1 : 0;
for (int i = 1; i < a.length; i++) {
dp[i][0] = Math.max(dp[i - 1][0], a[i] == b[0] ? 1 : 0);
}
for (int j = 1; j < b.length; j++) {
dp[0][j] = Math.max(dp[0][j - 1], b[j] == a[0] ? 1 : 0);
}
for (int i = 1; i < a.length; i++) {
for (int j = 1; j < b.length; j++) {
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
if (a[i] == b[j]) {
dp[i][j] = Math.max(dp[i - 1][j - 1] + 1, dp[i][j]);
}
}
}
return dp;
}
既然dp[][]矩阵已经构造完成,那么我们只要通过这个矩阵的状态就能得到最长公共子序列。步骤其实就是构建dp[][]过程的逆序。
①从dp[][]矩阵的右下角开始移动,可以有三种走法,向上,向左,向左上。用i表示行数,用j表示列,res表示结果。
②如果dp[i][j]大于dp[i-1][j]和dp[i][j-1],说明dp[i][j]是由dp[i-1][j-1]+1得来的,也就是说S1[i]==S2[j],该字符也是包含在最长公共子串的。所以把该字符加在res里。
③如果dp[i][j]等于dp[i-1][j],说明计算dp[i][j]的时候不是由dp[i-1][j-1]+1得来的。所以只需向上移动即可。
④如果dp[i][j]等于dp[i][j-1],说明计算dp[i][j]的时候不是由dp[i-1][j-1]+1得来的。所以只需向左移动即可。
⑤如果dp[i][j]等于dp[i][j-1]和dp[i-1][j],那么向上还是向左走都一样的,随意选一条无所谓。
public static void main(String[] args) {
String s1 = "abcd";
String s2 = "acbd";
int i = s1.length() - 1;
int j = s2.length() - 1;
int[][] dp = finddp(s1, s2);
char[] res = new char[dp[s1.length() - 1][s2.length() - 1]];
int k = res.length - 1;
while (k >= 0) {
if (i > 0 && dp[i - 1][j] == dp[i][j]) {
i--;
} else if (j > 0 && dp[i][j - 1] == dp[i][j]) {
j--;
} else {
res[k--] = s1.charAt(i);
i--;
j--;
}
}
System.out.println(String.valueOf(res));
}
注:以上借鉴程云老师的算法讲解。