import java.util.Scanner; /** * HDU 1159 * @author ashzheng * 2016年3月17日17:48:29 */ public class LongestCommonSubsequence { public static void main(String[] args) { Scanner scan = new Scanner(System.in); while (scan.hasNext()) { String s1 = new String(" " + scan.next()); //为了使字符串从下标1开始 String s2 = new String(" " + scan.next()); int[][] a = new int[s1.length()][s2.length()]; System.out.println(lcs(a, s1, s2)); printCommonSubsequence(a, s1.length() - 1, s2.length() - 1, new StringBuffer(), s1); int minSpace = Integer.min(s1.length(), s2.length()); int[] b = new int[minSpace]; System.out.println(s1.length() > s2.length() ? optimizeLcs(b, s1, s2) : optimizeLcs(b, s2, s1)); } } /** * 最长公共子序列问题 两个字符串中相同的部分最长为多少(可以不连续)。 * * 动态规划的思想,二维数组 a[i][j]表示 s1中前i个元素构成的字符串 与 s2中前j个元素构成的字符串的 最长公共子序列。可以发现: * 当s1[i]==s2[j]的时候 a[i][j] = a[i-1][j-1] + 1; * 当s1[i]!=s2[j]的时候 a[i][j] = max( a[i][j-1], a[i-1][j] ); * * @param a * 二维数组 a[i][j]表示 s1中前i个元素构成的字符串 与 s2中前j个元素构成的字符串的 最长公共子序列。 * @param s1 * 第一个字符串 * @param s2 * 第二个字符串 * @return 返回 最长公共子序列 的值。 */ public static int lcs(int[][] a, String s1, String s2) { for (int i = 0; i < s1.length(); i++) { for (int j = 0; j < s2.length(); j++) { if (i == 0 || j == 0) { a[i][j] = 0; // 由于在s1,s2字符串前面加了个字符,所以下标是从1开始的,这样可以省很多事 } else { if (s1.charAt(i) == s2.charAt(j)) { // 当s1[i]==s2[j]的时候 a[i][j] = a[i-1][j-1] + 1; a[i][j] = a[i - 1][j - 1] + 1; } else { // 当s1[i]!=s2[j]的时候 a[i][j] = max( a[i][j-1], a[i-1][j] ); a[i][j] = Integer.max(a[i - 1][j], a[i][j - 1]); } } } } return a[s1.length() - 1][s2.length() - 1];// 最后的结果就是这 } /** * 空间优化了的 最长公共子序列问题 空间复杂度从O(s1*s2) 降到 O(min(s1, s2)); * 优化的思想:通过观察你会发现,上面的代码中给a[i][j]中填充值的时候只用到了a[i - 1][j - 1],a[i - 1][j] 和 * a[i][j - 1]即a[i][j]的上面,左边,和 左上角 的元素 如果用一维数组表示的话 可以通过重复的利用节省空间,由于是重复利用,所以 * 原先元素上面的元素会存储在当前元素中,左边的还是左边,所以唯一需要记录的是左上角的元素,也就是上一次记录的左边的元素。 * * @param a * 二维数组 a[i][j]表示 s1中前i个元素构成的字符串 与 s2中前j个元素构成的字符串的 最长公共子序列。 * @param s1 * 第一个字符串 * @param s2 * 第二个字符串 * @return 返回 最长公共子序列 的值。 */ public static int optimizeLcs(int[] a, String s1, String s2) { for (int i = 0; i < s1.length(); i++) { int topLeft = 0; //记录上面左边的值,等同于a[i-1][j-1] for (int j = 0; j < s2.length(); j++) { if (i == 0 || j == 0) { topLeft = a[j]; //每次在a[j]改变前记录,为了让a[j+1]使用,它就是a[j+1]的上面左边的元素。 a[j] = 0; } else { if (s1.charAt(i) == s2.charAt(j)) { int temp = topLeft; topLeft = a[j]; a[j] = temp + 1; } else { topLeft = a[j]; a[j] = Integer.max(a[j - 1], a[j]); } } } } return a[s2.length() - 1]; } /** * 递归输出最长公共子序列,由于 * 当s1[i]==s2[j]的时候 a[i][j] = a[i-1][j-1] + 1; * 当s1[i]!=s2[j]的时候 a[i][j] = max( a[i][j-1], a[i-1][j] ); * * 所以如果上边的或者左边的元素跟当前元素一样的话,就证明当前这个字符在是不同的。否则是相同的,就要输出。当然这个输出的结果不是唯一的。 * * @param a 动态规划的状态数组 * @param i 动态规划的状态数组的下标 * @param j 动态规划的状态数组的下标 * @param buffer 记录要输出的最长公共子序列 * @param s1 两个比较字符串中的其中一个 */ public static void printCommonSubsequence(int a[][], int i, int j, StringBuffer buffer, String s1) { if (i < 1 || j < 1) { System.out.println("子序列为:" + buffer.reverse()); return; } if (a[i - 1][j] == a[i][j]) { //当前值与上边的值相同,说明字符不相同。是通过a[i][j] = a[i-1][j];得出来的 printCommonSubsequence(a, i - 1, j, buffer, s1); return; } if (a[i][j - 1] == a[i][j]) { //当前值与左边的值相同,说明字符不相同。是通过a[i][j] = a[i][j-1];得出来的 printCommonSubsequence(a, i, j - 1, buffer, s1); return; } buffer.append(s1.charAt(i)); //否则就是都不相同了,说明字符是相同的,是通过a[i-1][j-1]+1得出来的。 printCommonSubsequence(a, i - 1, j - 1, buffer, s1); } }