题目:
Description
给定两个字符串,返回两个字符串的最长公共子序列(不是最长公共子字符串),可能是多个。
Sample Input 1
1A2BD3G4H56JK
23EFG4I5J6K7
Sample Output 1
23G456K
23G45JK
思路:
先通过动态规划法求出每个位置的最长公共子序列长度的dp数组(从开始位置到当前位置)。再根据dp数组求解具体的最长公共子序列路径。代码copy同学的,主要是记录下代码-。-
代码:
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int numOfCases = Integer.parseInt(scanner.nextLine());
while (numOfCases-- > 0) {
// 接收原输入
String[] input1 = scanner.nextLine().split("");
String[] input2 = scanner.nextLine().split("");
// 两个数组从下标1开始存数据,便于后面算法使用
String[] seq1 = new String[input1.length+1];
String[] seq2 = new String[input2.length+1];
System.arraycopy(input1, 0, seq1, 1, input1.length); // (原数组, 原数组的开始位置, 目标数组, 目标数组的开始位置, 拷贝个数)
System.arraycopy(input2, 0, seq2, 1, input2.length);
// 解决问题
solveLCS(seq1, seq2);
// 清空记录所用集合
pathRecord.clear();
resultList.clear();
}
scanner.close();
}
static List<String> pathRecord = new ArrayList<>(); // 记录每次的路径
static List<String> resultList = new ArrayList<>(); // 存放最后得出的每个可行的路径
// 解决问题总思路
public static void solveLCS(String[] seq1, String[] seq2) {
/*
动态规划:最长公共子序列
X = {x1...xm} Y = {y1...yn}
Xi = {x1...xi} Yj = {y1...yj}
(1) 0 i==0 || j==0
dp[i][j] = (2) dp[i-1][j-1]+1 i>0 && j>0 && xi==yj
(3) max{dp[i-1][j],dp[i][j-1]} i>0 && j>0 && xi!=yj
*/
// 1. 初始化dpRec数组,用于记录的LCS的长度值(下标为0的行/列不表示任何信息)
int[][] dpRec = new int[seq1.length][seq2.length];
// 2. 根据两序列情况填写二维数组
for (int i = 1; i <= seq1.length-1; i++) {
for (int j = 1; j <= seq2.length-1; j++) {
if (seq1[i].equals(seq2[j])) {
dpRec[i][j] = dpRec[i-1][j-1] + 1;
} else {
dpRec[i][j] = Math.max(dpRec[i-1][j], dpRec[i][j-1]);
}
}
}
// 3. 根据dpRec,从右下角开始回溯输出路径
int pos1 = seq1.length-1;
int pos2 = seq2.length-1;
if (dpRec[pos1][pos2] == 0) {
// dpRec中全为零代表没有相同子序列,则按照题意直接结束
return;
}
findPath(seq1, seq2, dpRec, pos1, pos2);
// 4. 按照字典序输出结果集中的路径
resultList.sort(String::compareTo);
for (String s : resultList) {
System.out.println(s);
}
}
// 得到可行的路径,放入List result中
public static void findPath(String[] seq1, String[] seq2, int[][] dpRec, int pos1, int pos2) {
/*
根据统计时的算法,根据当前位置值的来源倒推回去。
如果当前位置两字符串字符相同,则放入结果栈
*/
if (dpRec[pos1-1][pos2-1] == 0 && dpRec[pos1-1][pos2] == 0 && dpRec[pos1][pos2-1] == 0) {
// 值的可能来源全部为零,则表示已回溯至第一个点,而第一个有值的位置一定是两字符相同的位置
pathRecord.add((seq1[pos1])); // 压入第一个点
String path = "";
for (int i = pathRecord.size()-1; i >= 0; i--) {
path += pathRecord.get(i);
}
if (!resultList.contains(path)) { // 防止重复
resultList.add(path); // 放入结果集中
}
pathRecord.remove(pathRecord.size()-1);
} else {
// 未回溯至第一个点
// 判断当前位置两字符是否相同
if (seq1[pos1].equals(seq2[pos2])) {
// 当前位置两字符相同
// 则当前字符放入结果栈
pathRecord.add(seq1[pos1]);
// 且值的来源为dpRec[pos1-1][pos2-1]; 递归继续寻找
findPath(seq1, seq2, dpRec, pos1-1, pos2-1);
pathRecord.remove(pathRecord.size()-1);
} else {
// 两位置字符不同
// 值的来源为dpRec[pos1-1][pos2]与dpRec[pos1][pos2-1]中较大的那个
// 分三种情况 大于小于和等于
if (dpRec[pos1-1][pos2] > dpRec[pos1][pos2-1]) {
// 大于
findPath(seq1, seq2, dpRec, pos1-1, pos2);
} else if (dpRec[pos1-1][pos2] < dpRec[pos1][pos2-1]) {
// 小于
findPath(seq1, seq2, dpRec, pos1, pos2-1);
} else {
// 等于(产生分支)
findPath(seq1, seq2, dpRec, pos1-1, pos2);
findPath(seq1, seq2, dpRec, pos1, pos2-1);
}
}
}
}
}