1.区别
找两个字符串的最长公共子串,这个子串要求在原字符串中是连续的。而最长公共子序列则并不要求连续。
2、最长公共子串
其实这是一个序贯决策问题,可以用动态规划来求解。我们采用一个二维矩阵来记录中间的结果。这个二维矩阵怎么构造呢?直接举个例子吧:"bab"和"caba"(当然我们现在一眼就可以看出来最长公共子串是"ba"或"ab")
b a b
c 0 0 0
a 0 1 0
b 1 0 1
a 0 1 0
我们看矩阵的斜对角线最长的那个就能找出最长公共子串。
不过在二维矩阵上找最长的由1组成的斜对角线也是件麻烦费时的事,下面改进:当要在矩阵是填1时让它等于其左上角元素加1。
b a b
c 0 0 0
a 0 1 0
b 1 0 2
a 0 2 0
这样矩阵中的最大元素就是 最长公共子串的长度。
在构造这个二维矩阵的过程中由于得出矩阵的某一行后其上一行就没用了,所以实际上在程序中可以用一维数组来代替这个矩阵。
2.1 代码如下:
public class LCString2 {
public static void getLCString(char[] str1, char[] str2) {
int i, j;
int len1, len2;
len1 = str1.length;
len2 = str2.length;
int maxLen = len1 > len2 ? len1 : len2;
int[] max = new int[maxLen];
int[] maxIndex = new int[maxLen];
int[] c = new int[maxLen]; // 记录对角线上的相等值的个数
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;
}
if (c[j] > max[0]) { // 如果是大于那暂时只有一个是最长的,而且要把后面的清0;
max[0] = c[j]; // 记录对角线元素的最大值,之后在遍历时用作提取子串的长度
maxIndex[0] = j; // 记录对角线元素最大值的位置
for (int k = 1; k < maxLen; k++) {
max[k] = 0;
maxIndex[k] = 0;
}
} else if (c[j] == max[0]) { // 有多个是相同长度的子串
for (int k = 1; k < maxLen; k++) {
if (max[k] == 0) {
max[k] = c[j];
maxIndex[k] = j;
break; // 在后面加一个就要退出循环了
}
}
}
}
}
for (j = 0; j < maxLen; j++) {
if (max[j] > 0) {
System.out.println("第" + (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 = new String("123456abcd567");
String str2 = new String("234dddabc45678");
// String str1 = new String("aab12345678cde");
// String str2 = new String("ab1234yb1234567");
getLCString(str1.toCharArray(), str2.toCharArray());
}
}
2.最长公共子序列
1)最长公共子序列的长度的动态规划方程
设有字符串a[0...n],b[0...m],下面就是递推公式。字符串a对应的是二维数组num的行,字符串b对应的是二维数组num的列。
另外,采用二维数组flag来记录下标i和j的走向。数字"1"表示,斜向下;数字"2"表示,水平向右;数字"3"表示,竖直向下。这样便于以后的求解最长公共子序列。
(2)求解公共子序列代码
#include<stdio.h>
#include<string.h>
char a[500],b[500];
char num[501][501]; ///记录中间结果的数组
char flag[501][501]; ///标记数组,用于标识下标的走向,构造出公共子序列
void LCS(); ///动态规划求解
void getLCS(); ///采用倒推方式求最长公共子序列
int main()
{
int i;
strcpy(a,"ABCBDAB");
strcpy(b,"BDCABA");
memset(num,0,sizeof(num));
memset(flag,0,sizeof(flag));
LCS();
printf("%d\n",num[strlen(a)][strlen(b)]);
getLCS();
return 0;
}
void LCS()
{
int i,j;
for(i=1;i<=strlen(a);i++)
{
for(j=1;j<=strlen(b);j++)
{
if(a[i-1]==b[j-1]) ///注意这里的下标是i-1与j-1
{
num[i][j]=num[i-1][j-1]+1;
flag[i][j]=1; ///斜向下标记
}
else if(num[i][j-1]>num[i-1][j])
{
num[i][j]=num[i][j-1];
flag[i][j]=2; ///向右标记
}
else
{
num[i][j]=num[i-1][j];
flag[i][j]=3; ///向下标记
}
}
}
}
void getLCS()
{
char res[500];
int i=strlen(a);
int j=strlen(b);
int k=0; ///用于保存结果的数组标志位
while(i>0 && j>0)
{
if(flag[i][j]==1) ///如果是斜向下标记
{
res[k]=a[i-1];
k++;
i--;
j--;
}
else if(flag[i][j]==2) ///如果是斜向右标记
j--;
else if(flag[i][j]==3) ///如果是斜向下标记
i--;
}
for(i=k-1;i>=0;i--)
printf("%c",res[i]);
}
(3)图示