题目:
求解两个字符串的最长公共子序列。如 AB34C 和 A1BC2 则最长公共子序列为 ABC。
思路分析:可以用dfs深搜,这里使用到了前面没有见到过的双重循环递归。也可以使用动态规划,在建表的时候一定要注意初始化以及在发现规律的时候一定要想怎么利用前面已经算过的结果来得到现在的结果,或者利用其他的一些规律来发现能够解题的规律。
图中单元格需要填上相应的数字(这个数字就是dp[i][j]的定义,记录的LCS的长度值)。可以发现规律,简单来说:如果横竖(i,j)对应的两个元素相等,该格子的值 = c[i-1,j-1] + 1。如果不等,取c[i-1,j] 和 c[i,j-1]的最大值。
当得到完整的DP表之后,我们可以通过倒推来得到相应的子序列,有时S1和S2的LCS并不是只有1个,本题并不是着重说要输出两个序列的所有LCS,只是要输出其中一个LCS。
代码:
import java.util.ArrayList; public class LCS { public static void main(String[] args) { ArrayList ans = dfs("AB34C", "A1BC2"); System.out.println(ans); // 输出 [A, B, C] System.out.println(dfs("3563243", "513141")); // 输出 [5, 3, 4] System.out.println(solution("3069248", "513164318")); // 输出 [3, 6, 4, 8] System.out.println(solution("123", "456")); // 输出为空 } // 双重循环递归 static ArrayList<Character> dfs(String s1, String s2) { int len1 = s1.length(); int len2 = s2.length(); ArrayList<Character> ans = new ArrayList<>(); for (int i = 0; i < len1; i++) { // 求以i字符开头的公共子序列 ArrayList<Character> list = new ArrayList<>(); // 和s2的每个字符比较 for (int j = 0; j < len2; j++) { if (s1.charAt(i) == s2.charAt(j)) {// 如果相同 list.add(s1.charAt(i)); list.addAll(dfs(s1.substring(i + 1), s2.substring(j + 1))); break; } } if (list.size() > ans.size()) { ans = list; } } return ans; } /** * 生成动规表 */ static String solution(String s1, String s2) { int len1 = s1.length(); int len2 = s2.length(); int[][] dp = new int[len1 + 1][len2 + 1]; // 动规数组 int flag = 0; // 初始化第一列 // O(M) for (int i = 1; i <= len1; i++) { if (flag == 1) { dp[i][1] = 1; } else if (s1.charAt(i - 1) == s2.charAt(0)) { dp[i][1] = 1; flag = 1; } else { dp[i][1] = 0; } } flag = 0; // 初始化第一行 // O(N) for (int j = 1; j <= len2; j++) { if (flag == 1) { dp[1][j] = 1; } else if (s2.charAt(j - 1) == s1.charAt(0)) { dp[1][j] = 1; flag = 1; } else { dp[1][j] = 0; } } // O(M*N) for (int i = 2; i <= len1; i++) { // M for (int j = 2; j <= len2; j++) { // N int maxOfLeftAndUp = Math.max(dp[i - 1][j], dp[i][j - 1]); if (s1.charAt(i - 1) == s2.charAt(j - 1)) { // dp[i][j] = Math.max(maxOfLeftAndUp, dp[i - 1][j - 1] + 1); dp[i][j] = dp[i - 1][j - 1] + 1;// 这样也是对的…… } else { dp[i][j] = maxOfLeftAndUp; } } } return parseDp(dp, s1, s2); } /** * 解析动态规划表,得到最长公共子序列 */ private static String parseDp(int[][] dp, String s1, String s2) { int M = s1.length(); int N = s2.length(); StringBuilder sb = new StringBuilder(); while (M > 0 && N > 0) { // 比左和上大,一定是当前位置的字符相等 if (dp[M][N] > Math.max(dp[M - 1][N], dp[M][N - 1])) { sb.insert(0, s1.charAt(M - 1)); M--; N--; } else { // 一定选择的是左边和上边的大者 if (dp[M - 1][N] > dp[M][N - 1]) { M--; // 往上移 } else { N--; // 往左移 } } } return sb.toString(); } }