##问题描述
最长公共子串跟LCS的区别在于这个子串要求在原字符串中是连续的,而最长公共子序列则并不要求连续
可以参考 LCS问题求解
##动态规划求解
为了节约重复求相同子问题的时间,引入一个数组,不管它们是否对最终解有用,把所有子问题的解存于该数组中,这就是动态规划法所采用的基本方法。
如果在什么想法都没有的情况下建个表,那我们会希望坐标轴上是两个子串,里面的内容是什么呢?比如可以这样,当字符不相等的时候记为0,不相等的记1,很好,那么最长对角线对应的就是最长子串!
b a b
c 0 0 0
a 0 1 0
b 1 0 1
a 0 1 0
但是在一个二维矩阵上找最长的 由1组成的斜对角线是不好实现的,可作如下改进:当要在矩阵中填1时,让它等于其左上角元素加1,这样不仅方便操作,还能get最长公共子串的长度,就是矩阵中的最大元素
b a b
c 0 0 0
a 0 1 0
b 1 0 2
a 0 2 0
然而,这个问题还是可以优化的:“在构造这个二维矩阵的过程中由于得出矩阵的某一行后其上一行就没用了,所以实际上在程序中可以用一维数组来代替这个矩阵。”
这句话怎么理解呢?我们需要的只有一个最大长度和一个标记最大长度的位置,并不会再用到矩阵里其他的值,这样一来,只要记录完我们需要的,数据用完就可以丢掉了。具体的实现看代码
完整java代码如下:
public class LCSubstring {
private static void printLCString(char[] str1, char[] str2) {
int len1 = str1.length;
int len2 = str2.length;
int maxLen = len1 > len2 ? len1 : len2;
// max[]记录最大长度
// maxIndex[]记录最大长度对应的字符位置
// c[]就是一维数组建立的"表格"
int[] max = new int[maxLen];
int[] maxIndex = new int[maxLen];
int[] c = new int[maxLen];
int i, j;
for (i = 0; i < len2; i++) {
for (j = len1 - 1; j >= 0; j--) {
if (str2[i] == str1[j]) {
if (i == 0 || j == 0) {
c[j] = 1;
} else {
c[j] = c[j - 1] + 1;
}
} else {
c[j] = 0;
}
// max[0]表示最先达到最大长度,对应maxIndex[0]
// 如果发现与已存在的最大长度相等的,记在max[]数组的后面位置
if (c[j] == max[0]) {
for (int k = 1; k < maxLen; k++) {
if (max[k] == 0) {
max[k] = c[j];
maxIndex[k] = j;
break;
}
}
}
// 如果发现更大长度,首位置替换,后面清零
else if (c[j] > max[0]) {
max[0] = c[j];
maxIndex[0] = j;
for (int k = 1; k < maxLen; k++) {
max[k] = 0;
maxIndex[k] = 0;
}
}
}
// 打印c[]表格
for (int temp : c) {
System.out.print(temp);
}
System.out.println();
}
// 打印最长公共子串
for (j = 0; j < maxLen; j++) {
if (max[j] > 0) {
System.out.print("第" + (j + 1) + "个公共子串:");
for (i = maxIndex[j] - max[j] + 1; i <= maxIndex[j]; i++) {
System.out.print(str1[i]);
}
System.out.println();
}
}
}
public static void main(String[] args) {
String str1 = "xzyzzyx";
String str2 = "zxyyzxz";
printLCString(str1.toCharArray(), str2.toCharArray());
String str3 = "MAEEEVAKLEKHLMLLRQEYVKLQKKLAETEKRCALLAAQANKESSSESFISRLLAIVAD";
String str4 = "MAEEEVAKLEKHLMLLRQEYVKLQKKLAETEKRCTLLAAQANKENSNESFISRLLAIVAG";
printLCString(str3.toCharArray(), str4.toCharArray());
}
}
2019-10-14补充
为什么要一个从前往后,一个从后往前,即
i:0 -> len2-1
j:len1-1 -> 0
因为语句 c[j] = c[j - 1] + 1; 要求前面的一行还是原来的值