求最长公共子序列,可以用典型的动态规划解法,上一篇求字符串编辑距离的dp解法也可以说是由这个案例变形而来。
状态变量:
dp[i][j]表示字符串a[i]和字符串b[j]的最长公共子序列
状态转移方程:
if(a[i-1]==b[j-1]) dp[i][j]=dp[i-1][j-1]+1;
else dp[i][j]=max{dp[i-1][j],dp[i][j-1]};
回溯路径:
得到dp表格后,可直接得到a[0..m]与b[0..n]的最长公共子序列dp[m+1][n+1];
若想得到具体的字符序列,则可以根据表格采用回溯的方法得到,也可在状态转移的过程中求出每种状态的的双亲节点,最后通过堆栈就可以把序列正序输出。
import java.util.Scanner;
import java.util.Stack;
/**
*
* @author Administrator 动态规划求两个序列的最长公共子序列
*/
public class LCS {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
int num = scan.nextInt();
while (num-- != 0) {
char[] a = scan.next().toCharArray();
char[] b = scan.next().toCharArray();
int[][] dp = new int[a.length + 1][b.length + 1]; //dp[i][j]代表a[0..i-1]与b[0..j-1]最长公共子序列
int[][] path = new int[a.length + 1][b.length + 1];
//边界处理
for (int i = 0; i <= a.length; i++) {
dp[i][0] = 0;
}
for (int j = 0; j <= b.length; j++) {
dp[0][j] = 0;
}
//状态转移方程
for (int i = 1; i <= a.length; i++) {
for (int j = 1; j <= b.length; j++) {
if (a[i - 1] == b[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + 1;
path[i][j] = 1; //记录双亲节点
} else {
dp[i][j] = Math.max(dp[i][j - 1], dp[i - 1][j]);
path[i][j] = dp[i][j - 1] > dp[i - 1][j] ? 2 : 3;
//记录双亲节点
}
}
}
System.out.println(dp[a.length][b.length]);
//根据dp表格通过回溯找到最优路径
Stack<Character> stack = new Stack();
int x = a.length, y = b.length;
while (x != 0 && y != 0) {
if (a[x - 1] == b[y - 1]) {
stack.push(a[x - 1]);
x--;
y--;
} else if (dp[x - 1][y] >= dp[x][y - 1]) {
x--;
} else {
y--;
}
}
while (!stack.empty()) {
System.out.print(stack.pop());
}
System.out.println();
// print(a.length,b.length,path,a);
// System.out.println();
}
}
public static void print(int x, int y, int[][] path, char[] a) {
if(x==0||y==0){
return;
}
if (path[x][y] == 1) { //从左上角转移过来
print(x - 1, y - 1, path, a);
System.out.print(a[x-1]);
} else if(path[x][y]==2){
print(x,y-1,path,a);
}else{
print(x-1,y,path,a);
}
}
}