最长公共子序列即是两个字符串都包含的一个字符序列,但是不需要连续。例如:
String s1 : abfc
String s2: abcd
"abc"既是s1的一个子序列,也是s2的一个子序列,因此"abc"是他们的最长公共子序列。
简单总结下思路: 使用动态规划,用一个二维数组dp[i][j]表示 s1的1--i与s2的1--j的子串的最长公共子序列。例如:dp[2][3] 表示s1的 ”ab“ 和s2的 ”abc“的最长公共子序列。由子问题的结果逐步往上得到最终解。动态规划的最关键就是递推的定义最优解,最长公共子序列的递推公式为:
dp[i][j] = dp[i-1][j-1] + 1 (ch1[i] == ch2[j])
dp[i][j] = Max(dp[i-1][j] , dp[i][j-1]) (ch1[i] != ch2[j])
(ch1[] ch2[] 分别为s1 s2转化来的字符数组)
dp[i][j] = 0 (i==0 || j == 0)
代码如下:
import java.util.*; public class LCS { public static void main (String [] args) { Scanner sc = new Scanner(System.in); while(sc.hasNext()) { //输入两个字符串 String s1 = sc.next(); String s2 = sc.next(); System.out.println("\n"+LCSLen(s1,s2)); } } //最长公共子序列的长度 static int LCSLen(String s1,String s2) { char [] ch1 = s1.toCharArray(); char [] ch2 = s2.toCharArray(); int[][] flag = new int[ch1.length+1][ch2.length+1]; //flag[i][j]记录dp[i][j]的值是如何得来的 int [][] dp = new int[ch1.length+1][ch2.length+1]; //dp[i][j]表示ch1的0--i子串 与 ch2的0--j子串 的最长公共子序列长度 for(int i = 1; i <= ch1.length; i++) { for(int j = 1; j <= ch2.length; j++) { if(ch1[i-1] == ch2[j-1]) { dp[i][j] = dp[i-1][j-1] + 1; //ch1的i位置与ch2的j位置二者字符相同,则长度可以+1 flag[i][j] = 1; } else if(dp[i-1][j] >= dp[i][j-1]) { //否则取dp[i-1][j] dp[i][j-1]二者中较大值,二者相等这里取前者。 dp[i][j] = dp[i-1][j]; flag[i][j] = 2; } else if(dp[i][j-1] > dp[i-1][j]) { dp[i][j] = dp[i][j-1]; flag[i][j] = 3; } } } getLCS(s1,s1.length(),s2.length(),flag); return dp[ch1.length][ch2.length]; } //构造一个最长公共子序列 static void getLCS(String s1,int n,int m,int[][] f) { StringBuffer sb = new StringBuffer(); char[] ch = s1.toCharArray(); while(n > 0 && m > 0) { if(f[n][m] == 1) { //根据flag[][]记录的标志找出一个最长公共子序列 sb.append(ch[n-1]); n--; m--; } if(f[n][m] == 2) { //dp[i-1][j] >=dp[i][j-1],所以s1的当前字符应为更左边一个,n-- n--; } if(f[n][m] == 3) { m--; } } System.out.println(sb.reverse()); //上述字符都是从右至左挑选的,所以要反转一下输出 } }