给定两个字符串str1和str2,返回两个字符串的最长公共子序列。
【题目】
给定两个字符串str1和str2,返回两个字符串的最长公共子序列。
【举例】
str1="1A2C3D4B56",str2="B1D23CA45B6A"。
"123456"或者"12C4B6"都是最长公共子序列,返回哪一个都行。
【解答】
本题是非常经典的动态规划问题,先来介绍求解动态规划表的过程。
如果str1的长度为M,str2的长度为N,生成大小为M×N的矩阵dp,行数为M,列数为N。
dp[i][j]的含义是str1[0..i]与str2[0..j]的最长公共子序列的长度。
从左到右,再从上到下计算矩阵dp。
1.矩阵dp第一列即dp[0..M-1][0],dp[i][0]的含义是str1[0..i]与str2[0]的最长公共子序列长度。
str2[0]只有一个字符,所以dp[i][0]最大为1。如果str1[i]==str2[0],令dp[i][0]=1,一旦dp[i][0]被设置为1,之后的dp[i+1..M-1][0]也都为1。
比如,str1[0..M-1]=”ABCDE”,str2[0]=”B”。str1[0]为”A”,与str2[0]不相等,
所以dp[0][0]=0。str1[1]为”B”,与str2[0]相等,所以str1[0..1]与str2[0]的最长公共子序列为”B”,
令dp[1][0]=1。之后的dp[2..4][0]肯定都是1,
因为str[0..2]、str[0..3]和str[0..4]与str2[0]的最长公共子序列肯定有”B”。
2.矩阵dp第一行即dp[0][0..N-1]与步骤1同理,如果str1[0]==str2[j],则令dp[0][j]=1,
一旦dp[0][j]被设置为1,之后的dp[0][j+1..N-1]也都为1。
3.对其他位置(i,j),dp[i][j]的值只可能来自以下三种情况:
● 可能是dp[i-1][j],代表str1[0..i-1]与str2[0..j]的最长公共子序列长度。
比如,str1=”A1BC2”,str2=”AB34C”。str1[0..3]
(即”A1BC”)与str2[0..4](即”AB34C”)的最长公共子序列为”ABC”,
即dp[3][4]为3。str1[0..4](即”A1BC2”)与str2[0..4](即”AB34C”)
的最长公共子序列也是”ABC”,所以dp[4][4]也为3。
● 可能是dp[i][j-1],代表str1[0..i]与str2[0..j-1]的最长公共子序列长度。
比如,str1=”A1B2C”,str2=”AB3C4”。
str1[0..4](即”A1B2C”)与str2[0..3](即”AB3C”)
的最长公共子序列为”ABC”,即dp[4][3]为3。
str1[0..4](即”A1B2C”)与str2[0..4](即”AB3C4”)的最长公共子序列也是”ABC”,
所以dp[4][4]也为3。
● 如果str1[i]==str2[j],还可能是dp[i-1][j-1]+1。
比如str1=”ABCD”,str2=”ABCD”。str1[0..2](即”ABC”)与str2[0..2](即”ABC”)
的最长公共子序列为”ABC”,即dp[2][2]为3。因为str1[3]==str2[3]==”D”,
所以str1[0..3]与str2[0..3]的最长公共子序列是”ABCD”。
这三个可能的值中,选最大的作为dp[i][j]的值。
public static String getdp(char[] str1,char[] str2){
if(str1==null||str2==null||str1.length==0||str1.length==0){
return "";
}
int m=str1.length;
int n=str2.length;
//dp[i][j]对应str1[0...i]和str2[0...j]的最长公共子序列
int[][] dp=new int [m][n];
dp[0][0]=(str2[0]==str1[0])?1:0;
//初始化第一行
for (int i = 1; i < n; i++) {
if(str2[i]==str1[0]||dp[0][i-1]==1){
dp[0][i]=1;
}else{
dp[0][i]=0;
}
}
//初始化第一列
for (int i = 1; i < m; i++) {
if(str1[i]==str2[0]||dp[i-1][0]==1){
dp[i][0]=1;
}else{
dp[i][0]=0;
}
}
//初始化dp[i][j]
for (int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
dp[i][j]=Math.max(dp[i][j-1], dp[i-1][j]);
if(str1[i]==str2[j]){
//str1[i]==str2[j],对应的dp[i][j]应该增1,str1[i]就是公共子序列的一部分
dp[i][j]=Math.max(dp[i][j],dp[i-1][j-1]+1);
}
}
}
char[] res=new char[dp[m-1][n-1]];//dp[m-1][n-1]对应最长公共子序列长度
int index=res.length-1;
m--;
n--;
//从下往上获取最长公共子序列
while(index>=0) {
if(m>0&&dp[m][n]==dp[m-1][n]){
m--;
}else if(n>0&&dp[m][n]==dp[m][n-1]){
n=n-1;
}else{
res[index--]=str1[m];
m=m-1;
n=n-1;
}
}
return String.valueOf(res);
}
测试一下:
public static void main(String[] args) {
String s1="1a2c3d4b56";
String s2="b1d23ca45b6a";
char[] str1=s1.toCharArray();
char[] str2=s2.toCharArray();
System.out.println(getdp(str1, str2));
}
12c4b6