最长公共子序列
Description
给定两个字符串,返回两个字符串的最长公共子序列(不是最长公共子字符串),可能是多个。
Input
输入第一行为用例个数, 每个测试用例输入为两行,一行一个字符串
Output
如果没有公共子序列,不输出,如果有多个则分为多行,按字典序排序。
Sample Input 1
1 1A2BD3G4H56JK 23EFG4I5J6K7
Sample Output 1
23G456K 23G45JK
解题思路:
先看一下对于两个字符串str1和str2,能不能利用已有的信息推出进一步的信息。
这里用n表示str1长度,m表示str2长度,有i∈[0,n-1] , j∈[0,m-1], dp[i][j]表示str1从0-i 与 str2从o-j的最长公共子序列长度。
要计算dp[i][j],可以这样分情况讨论:
如果str1[i]=str2[j], 那么肯定最大公共子序列包含这个字符,则dp[i][j] = dp[i-][j-1]+1;
如果str1[i]!=str2[j],那么继续判断:
比较dp[i-1][j]与dp[i][j-1],哪个大就选择哪一个,一样大,说明都可以选择。dp[i][j] = Math.max(dp[i][j-1],dp[i-1][j])
然后可以将递归的过程用递推实现。
实现代码如下:
1 int[][] dp = new int[str1.length()+1][str2.length()+1]; 2 int n = str1.length()+1; 3 int m = str2.length()+1; 4 // 计算dp数组 5 for (int i = 1; i < n; i++) { 6 for (int j = 1; j < m; j++) { 7 if (str1.charAt(i-1) == str2.charAt(j-1)) 8 dp[i][j] = dp[i-1][j-1]+1; 9 else 10 dp[i][j] = Math.max(dp[i-1][j], dp[i][j-1]); 11 } 12 }
计算出长度后,要求出公共子序列,可以根据dp[][]反推回去。
令i=n-1, j=m-1。
如果str1[i]=str2[j],那么当前点一定包含在最长子序列中,接下来直接进入dp[i-1][j-1];
如果str1[i]!=str2[j],继续判断:
比较dp[i-1][j]与dp[i][j-1],哪个大就选择哪一个,进入Math.max(dp[i][j-1],dp[i-1][j]),但是不记录字符,因为不是两边字符串相同字符。
这里比较特殊的是dp[i][j-1]=dp[i-1][j]的情况,这里都进入的话可能会有重复字符串,所以最后需要去重。
完整代码如下:
1 import java.util.*; 2 3 public class Main { 4 5 public static void main(String[] args) { 6 Scanner scan = new Scanner(System.in); 7 int x = scan.nextInt(); 8 scan.nextLine(); 9 // n个测试样例 10 for (int k = 0; k < x; k++){ 11 String str1 = scan.nextLine(); 12 String str2 = scan.nextLine(); 13 int[][] dp = new int[str1.length()+1][str2.length()+1]; 14 int n = str1.length()+1; 15 int m = str2.length()+1; 16 // 计算dp数组 17 for (int i = 1; i < n; i++) { 18 for (int j = 1; j < m; j++) { 19 if (str1.charAt(i-1) == str2.charAt(j-1)) 20 dp[i][j] = dp[i-1][j-1]+1; 21 else 22 dp[i][j] = Math.max(dp[i-1][j], dp[i][j-1]); 23 } 24 } 25 // 逆向推导公共子序列,因为需要计算所有可能的结果,所以这里用dfs实现 26 List<String> lists = new ArrayList<>(); 27 StringBuilder sb = new StringBuilder(); 28 dfs(lists, sb, dp, str1, str2, n-1, m-1); 29 // 输出lists 30 if (!lists.isEmpty()) { 31 Collections.sort(lists, String::compareTo); 32 for (int i = 0; i < lists.size(); i++) { 33 if (i == 0 || !lists.get(i).equals(lists.get(i-1))) 34 System.out.println(lists.get(i)); 35 } 36 37 } 38 } 39 40 } 41 // dfs推导公共子序列 42 public static void dfs(List<String> lists, StringBuilder sb, int[][] dp, String str1, String str2, int i, int j) { 43 // 如果超出范围或者dp[i][j]=0,则退出递归,退出前将list插入lists 44 if (dp[i][j] == 0 || i == 0 || j == 0) { 45 if (sb.length() != 0) { 46 StringBuilder temp = new StringBuilder(sb); 47 temp.reverse(); 48 lists.add(temp.toString()); 49 } 50 return; 51 } 52 53 // 如果str1.charAt(i-1) == str2.charAt(j-1),则直接记录并进入dp[i-1][j-1] 54 if (str1.charAt(i-1) == str2.charAt(j-1)) { 55 sb.append(str1.charAt(i-1)); 56 dfs(lists, sb, dp, str1, str2, i-1, j-1); 57 // 因为有分支,所以这里回溯下sb 58 sb.deleteCharAt(sb.length()-1); 59 } else { 60 // 如果str1.charAt(i-1) != str2.charAt(j-1),则比较dp[i-1][j]和dp[i][j-1],进入更大的那个 61 if (dp[i-1][j] == dp[i][j-1]) { 62 dfs(lists, sb, dp, str1, str2, i-1, j); 63 dfs(lists, sb, dp, str1, str2, i, j-1); 64 } else if (dp[i-1][j] > dp[i][j-1]) { 65 dfs(lists, sb, dp, str1, str2, i-1, j); 66 } else { 67 dfs(lists, sb, dp, str1, str2, i, j-1); 68 } 69 } 70 71 } 72 }