Longest Common Subsequence,LCS算法

问题:给出两个字符串a和b,求出他们的最长公共子序列。例如,a是"aabcadabdc“, b是"bcdbaac",则他们的公共子序列时”bcaac“。

注:子序列中的字符可以不相连,但字符的相对顺序不能改变

 

思路:假设已经匹配到字符串a和字符串b的末尾,则可能出现三种情况

1、a[m]==b[n],则lcs的最后一个字符lcs[k]=a[m]=b[n];

2、a[m]!=b[n],且lcs的最后一个字符lcs[k]!=a[m],则lcs的最终匹配结果来自于字符串的匹配结果

3、a[m]!=b[n],且lcs的最后一个字符lcs[k]!=b[n],则lcs的最终匹配结果来自于字符串a[1.....m]和字符串b[1.....n-1]的匹配结果

以此思路类推,则有当匹配到a的第i个字符,b的第j个字符时,有状态转移方程:

1、若a[i]==b[j],则当前lcs的长度加一

2、若a[i]!=b[j],则当前lcs的长度等于字符串a[1.....i-1]和b[1.....j]进行匹配、字符串a[1.....i]和b[1.....j-1]进行匹配,这两者中的最大值

 

具体实现方法:

使用二维数组dp[m+1][n+1]记录匹配到(i,j)时的lcs长度。

使用二维数组flag[m+1][n+1]记录对字符串a,b进行回溯时的回溯路径。flag[i][j]==1表示字符a[i]==b[j],应该回溯到(i-1,j-1)的状态;flag[i][j]==2表示当前lcs的最后一个字符!=a[i],应该回溯到(i-1,j)的状态;flag[i][j]==3表示当前lcs的最后一个字符!=b[j],应该回溯到(i,j-1)的状态。

注:这里为了方便,假设数组和字符串从0开始

 

代码:

public class LCS_1 {
	public static void LCS(String a,String b) {
		int m=a.length(),n=b.length();
		int[][] dp=new int[m+1][n+1];
		//创建一个二维矩阵,用以记录当比较进行至a的第i个字符,b的第j个字符时,当前LCS的最长长度
		int[][] flag=new int[m+1][n+1];
		//创建一个二维矩阵,用以记录状态dp[i][j]的上一个状态是dp[i-1][j] dp[i][j-1] 还是dp[i-1][j-1]
		//数值1表示上一个状态是dp[i-1][j-1] 数值2表示上一个状态是dp[i-1][j] 数值3表示上一状态是dp[i][j-1]
		
		for(int i=1;i<=m;i++) {
			for(int j=1;j<=n;j++){
				if(a.charAt(i-1)==b.charAt(j-1)) {
					dp[i][j]=dp[i-1][j-1]+1;
					flag[i][j]=1;
				}
				else {
					dp[i][j]=Math.max(dp[i-1][j],dp[i][j-1]);
					if(dp[i][j]==dp[i-1][j]) flag[i][j]=2;
					else flag[i][j]=3;
				}
			}
		}

		StringBuffer temp=new StringBuffer();
		while(m>=1&&n>=1) {
			if(flag[m][n]==1) {
				temp.append(a.charAt(m-1));
				m--;
				n--;
			}
			else if(flag[m][n]==2) {
				m--;
			}
			else {
				n--;
			}
		}
		temp.reverse();
		
		System.out.println(temp);
	}
	
	public static void main(String[] args) {
		LCS("aabcadabdc","bcdbaac");
	}
}

时间复杂度是O(mn)+O(m+n),而暴力解法的时间复杂度是O(2^mn)。

你可能感兴趣的:(Longest Common Subsequence,LCS算法)