題目:
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);
}
}
}
}
}