问题描述:
给定两个字符串s1和s2;输出它们的最大公共子序列。
子序列和子数组不同,子数组是连续的;而子序列不要求是连续的,只要前后顺序关系一致就行。
例如:
输入:
AB34C
A1BC2
输出:
ABC
输入:
3563243
513141
输出:
534
解法一:
用递归的解法,遍历其中一个字符串,对其中的每一个字母在另一个字符串中找相同。找到后把剩余的字符串进行下一轮的递归。即在双层循环中使用递归。
代码如下:
import java.util.*;
public class 求最大公共子序列 {
public static void main(String[] args){
Scanner scanner = new Scanner(System.in);
String str1 = scanner.next();
String str2 = scanner.next();
System.out.println(lcs(str1, str2));
}
private static String lcs(String str1, String str2) {
StringBuilder res = new StringBuilder();
for(int i=0; i<str1.length(); i++){
StringBuilder sb = new StringBuilder();//用于记录每一个公共子序列
for(int j=0; j<str2.length(); j++){
if(str1.charAt(i)==str2.charAt(j)){
String temp = lcs(str1.substring(i+1, str1.length()),
str2.substring(j+1, str2.length()));
sb.append(str1.charAt(i)+temp);//进行记录
break;//完成查找,结束循环
}
}
if(sb.length()>res.length()){
res = sb;
}
}
return res.toString();
}
}
解法二:
用动态规划解决,难点主要在于dp表的建立,即找出dp表中的规律。我们可以用两个字符串s1和s2分别作为dp表的行和列。每一个单元格表示两个字符串当前范围内的最大公共子序列;然后初始化第一行和第一列;由于是第一行或者第一列,则有一个字符串的范围只有一个字符,所以第一行和第一列单元格的内容不是0就是1而且0只会出现在1的前面;也就是说当出现1后,则第一行或者第一列之后的单元格都是1。
在初始化第一行和第一列后;从第二行第二列开始建立dp表。dp表建立的规律就是:如果当前单元格的两个字符相等的话则就等于左上角的单元格的值加一以及左边单元格和上边单元格中的最大值;否则就等于左边单元格和上边单元格中的最大值。
由于dp最终得到的是最大公共子序列的长度;而题目要求的是输出最大公共子序列字符串。所以在建立好dp表以后还需要将dp表的结果逆推得出最大公共子序列。
代码如下:
import java.util.*;
public class 求最大公共子序列 {
public static void main(String[] args){
Scanner scanner = new Scanner(System.in);
String str1 = scanner.next();
String str2 = scanner.next();
int[][] dp = new int[str1.length()+1][str2.length()+1];
lcsdp(str1, str2, dp);
System.out.println(dp[str1.length()-1][str2.length()-1]);
}
/*
* 建立dp表
*/
private static void lcsdp(String str1, String str2, int[][] dp) {
int len1 = str1.length();
int len2 = str2.length();
boolean flag = false;
for(int i=0; i<len1; i++){// 初始化第一行
if(str1.charAt(i)==str2.charAt(0) || flag){
flag = true;
dp[0][i] = 1;
}else{
dp[0][i] = 0;
}
}
flag = false;
for(int i=0; i<len2; i++){// 初始化第一列
if(str1.charAt(0)==str2.charAt(i) || flag){
flag = true;
dp[i][0] = 1;
}else{
dp[i][0] = 0;
}
}
for(int i=1; i<len1; i++){
for(int j=1; j<len2; j++){
int max = Math.max(dp[i-1][j], dp[i][j-1]);//上边和下边的最大值
if(str1.charAt(i)==str2.charAt(j)){
dp[i][j]=Math.max(max, dp[i-1][j-1]+1);
}else{
dp[i][j] = max;
}
}
}
return ;
}
}