概念
一个给定序列的子序列是指在该序列中删除若干元素后得到的序列。例如,X={ A, B, C, D, A, B},X 的子序列有 { A, B, C, A,B }、{ A, B, D}、{ B, C, D, B }等。子序列和子串是不同的概念,子串的元素是在原序列中连续的。给定两个序列 X 和 Y,当另一序列 Z 既是 X 的子序列又是 Y 的子序列时,称 Z 是序列 X 和 Y 的一个公共子序列。最长公共子序列是长度最长的子序列。
LCS
最长公共子序列(Longest Common Subsequence, LCS)问题:给定两个序列 X 和 Y,找出 X 和 Y 的一个最长公共子序列用动态规划(DP)求 LCS,复杂度是 O(nm)(n,m是序列的长度)
例如,序列 X={ a, b, c, f, b, c }、Y={ a, b, f, c, a, b },如下图所示:
用 L[i][j] 表示子序列 Xi 和 Yj 的最长公共子序列的长度
当 Xi = Yj 时,找出 Xi-1 和 Yj-1 的最长公共子序列,然后在其尾部加上 Xi 即可得到 X 和 Y 的最长公共子序列
当 Xi != Yj 时,求解两个子问题:(1)求 Xi-1 和 Yj 的最长公共子序列;(2)求 Xi 和 Yj-1 的最长公共子序列。然后取其中的最大值
下面举例说明前几个步骤:
步骤 1:求 L[1][1]。有 X1 = Y1,得 L[1][1] = L[0][0] + 1 = 1,如下图:
步骤 2:求 L[1][2]。有 X1 != Y2,得 L[1][2] = max{ L[1][1], L[0][2] } = 1,如下图:
后续步骤:继续以上过程,最后得到的结果如下图,L[6][6] 就是答案
如果要输出方案,可采用回溯法:
由于表格中每一个数由上一个数或者左一个数或左斜上方一个数直接得到,因此可以从最后一个数开始回溯。每次调用至少向左或向上(或同时向左向上)移动一步,故最多调用(m+n)次就会遇到 i=0 或 j=0 的情况,此时开始返回。因此算法复杂度为 O(m+n)
代码如下(Java)`
public class Main {
int[][] dp = new int[1005][1005];
String str1, str2;
private int LCS(){
for(int i=0; i<1005; i++) //每次重置数组元素的值
Arrays.fill(dp[i],0);
char[] ch1 = str1.toCharArray();
char[] ch2 = str2.toCharArray();
for(int i=1; i<=ch1.length; i++)
for(int j=1; j<=ch2.length; j++){
if(ch1[i-1] == ch2[j-1])
dp[i][j] = dp[i-1][j-1] + 1;
else dp[i][j] = Math.max(dp[i-1][j], dp[i][j-1]);
}
return dp[ch1.length][ch2.length];
}
private String print(){
char[] ch1 = str1.toCharArray();
char[] ch2 = str2.toCharArray();
int m = ch1.length; //m 为 str1 的长度
int n = ch2.length; //n 为 str2 的长度
char[] result = new char[dp[m][n]];
int index = result.length-1; //从最后一个数开始回溯
while (index>=0){
if(n>0 && dp[m][n]==dp[m][n-1]) //向左移动
n--;
else if(m>0 && dp[m][n]==dp[m-1][n]) //向上移动
m--;
else { //向左斜上方移动
result[index--] = ch1[m-1]; //该位置即为需要记录的点
m--;
n--;
}
}
return String.valueOf(result);
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
Main m = new Main();
while (!sc.hasNext("#")){
m.str1 = sc.nextLine();
m.str2 = sc.nextLine();
System.out.println(m.LCS()); //DP 求最长公共子序列
System.out.println(m.print()); //打印出此最长公共子序列
}
sc.close();
}
}