首先我们要区分公共子序列和公共子串的区别,子串一定是字符串中连续长度的串,但是子序列却并无此要求,只要按顺序出现的相同元素,就可算做相同子序列。
首先给定两个字符串:“A B C B D A B"以及"B D C A B A”,在这里我们先排除掉暴力求解法来解决这个问题,我们可以将大规模的字符串给他拆分成小份的问题,我们记str1,str2分别为两个字符串,str1[i] 和 str2[j]分别代表字符串中的第i个元素和第j个元素。
- 我们只需要在 i 和 j 分别表示字符串末尾最后一个元素时,只需判断他的几种情况,首先若相等,则就是在str1[0] - str1[i-1]和str2[0] - str2[j-1]的字符串中最大公共子序列长度加1。
- 如果不相等,我们就在str1[0] - str1[i-1]和str2[0] - str2[j]的字符串中最大公共子序的长度或者str1[0] - str1[i]和str2[0] - str2[j-1]的字符串中最大公共子序的长度中选取最大值作为两个字符串的最终最大公共子序列。
经分析,我们做临界推移,假设每一个元素是字符串中的最后一个子元素,它只会有这三种情况,所以我们利用dp[ ][ ]做记录数组,就可得知最终的最大数值是多少。
package 经典算法;
import java.util.Scanner;
import java.util.Stack;
public class 最大公共子序列 {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
Stack stack = new Stack();
String str1 = in.next();
String str2 = in.next();
int m = str1.length();
int n = str2.length();
int[][] dp = new int[m+1][n+1];
int[][] bp = new int[m+1][n+1];
for(int i = 1;i <= m;i++) {
for(int j = 1;j <= n;j++) {
if(str1.charAt(i-1) == str2.charAt(j-1)) {
dp[i][j] = dp[i-1][j-1]+1;
bp[i][j] = 1;
}
else if(dp[i-1][j] < dp[i][j-1]) {
dp[i][j] = dp[i][j-1];
bp[i][j] = 2;
}
else {
dp[i][j] = dp[i-1][j];
bp[i][j] = 3;
}
}
}
/*
//System.out
for(int i = 0;i <= m;i++) {
for(int j = 0;j <= n;j++) {
System.out.print(dp[i][j]+" ");
}
System.out.println();
}
System.out.println();
//System.out
for(int i = 0;i <= m;i++) {
for(int j = 0;j <= n;j++) {
System.out.print(bp[i][j]+" ");
}
System.out.println();
}*/
while(true) {
if(m == 0 || n == 0)
break;
if(bp[m][n] == 1) {
stack.push(str1.charAt(m-1));
m--;
n--;
}
else if(bp[m][n] == 2)
n--;
else
m--;
}
System.out.println("最大公共子序列长度为:"+stack.size());
int temp = stack.size();
for(int i = 0;i < temp;i++)
System.out.print(stack.pop()+" ");
}
}
其中bp数组是为了记录我们每次所做的判断,以便后期回溯,找出最大公共子序列输出,如若不需要,可自行忽略,代码中所注释掉的部分,是对两个数组绘画完成后的输出,以便调试,也可自行忽略。
经典的LCS(最长公共子序列)问题可以以一举三,比如最为熟知的最长公共字串,就是在LCS的解题基础上,把三种判断删掉两种,只判断末尾元素是否相等,如若不等则该串断裂归0,其它串继续计数,最终统计记录数组中,字串最长的值,;最或者给定一个乱序字符串,让你求得他的最长连续字串是多少,这就可以理解为该字符串与其排序后的字符串求LCS解。