最长公共子序列(LCS)问题的解决及优化、最长公共子串问题

1、LCS:最长公共子序列问题

LCS问题的描述为:给定两个整数序列a和b,找到它们所有的公共子序列中最长的序列,输出其长度。

例:a:6 4 8 1 3 2、b:4 7 6 2 3 8 6 1

最长公共子序列有:6 8 1、4 8 1,长度均为3,因此结果为3。

解决LCS问题同样是动态规划思想。LCS问题的结果同时受到两个字符串的影响,所以状态需要用二维表示。我们以f[i][j]表示a中从1到i、b中从1到j范围内的最长公共子序列的长度,那么考虑对于状态f[i][j]可以由哪些状态转移过来,在每一阶段我们能做出的决策有以下几种:

若a[i]=b[j],则a[i]、b[j]同时属于公共子序列:

  • f[i][j]由f[i-1][j-1]转移而来,f[i][j]=f[i-1][j-1]+1;

若a[i]!=b[j],则a[i]、b[j]不能同时属于公共子序列:

  • a[i]不属于公共子序列:f[i][j]由f[i-1][j]转移而来,f[i][j]=f[i-1][j];
  • b[j]不属于公共子序列:f[i][j]由f[i][j-1]转移而来,f[i][j]=f[i][j-1];
  • a[i]、b[j]都不属于公共子序列:f[i][j]=f[i-1][j-1],这种决策可以舍去,因为只要f[i][j]发生了更新,都是由f[i-1][j-1]+1得到,其一定>f[i-1][j-1];

    该情形下舍去最后一项决策后,f[i][j]=max(f[i-1][j], f[i][j-1]);

综上所述,状态转移方程为:

gif.latex?f%5Bi%5D%5Bj%5D%3D%5Cleft%5C%7B%5Cbegin%7Bmatrix%7D%20f%5Bi-1%5D%5Bj-1%5D+1%2C%20a%5Bi%5D%3Db%5Bi%5D%5C%5C%20max%28f%5Bi%5D%5Bj-1%5D%2Cf%5Bi-1%5D%5Bj%5D%29%2C%20a%5Bi%5D%5Cneq%20b%5Bi%5D%20%5Cend%7Bmatrix%7D%5Cright.

f[a.length][b.length]即为最终要求得的结果,示例代码如下:

	public int Lcs(int[] a, int[] b) {
		int[][] f = new int[a.length+1][b.length+1];
		for(int i=1;i<=a.length;++i) {
			for(int j=1;j<=b.length;++j) {
				if(a[i-1]==b[j-1])
					f[i][j]=f[i-1][j-1]+1;
				else
					f[i][j]=Math.max(f[i-1][j], f[i][j-1]);
			}
		}
		return f[a.length()][b.length()];
	}

上面的代码需要两重循环,时间复杂度为gif.latex?O%28n%5E2%29

2、LCS问题的优化

当序列a中的元素都互不相同的时候,LCS存在一种优化方案,即将其转化为LIS问题求解,而LIS问题可以优化到O(nlogn)的时间复杂度内解决(最长上升子序列(LIS)问题的解决及优化)。那么如何将LCS问题转化为LIS呢,我们进行如下分析:

我们对序列a中的元素按其下标进行标号,因为a中的元素都互不相同,所以每个元素的标号都是唯一的且递增的。而最长公共子序列它属于a的子序列,那么它的元素的标号也是递增的,同时它也属于b的子序列,反过来说也就是当b的某个子序列按a的标号方式呈递增的时候,它也属于a的子序列,即它是a、b的公共子序列。我们可以对b中的元素按a的标号方式标号,剩下的就是从里面寻找递增子序列,又要找最长的那个,那问题就变成了最长上升子序列即LIS问题。

例:a:6 4 8 1 3 2 ,b:4 7 6 2 3 8 6 1

标号为: 0 1 2 3 4 5,则序列b变为:1 0 5 4 2 0 3

标号后的b序列的最长上升子序列为:0 2 3,1 2 3

对应在序列a中得到最长公共子序列为:6 8 1,4 8 1

	public int LCS(int[] a, int[] b) {
		HashMap<Integer,Integer> map = new HashMap<>();
		for(int i=0;i<a.length;++i)
			map.put(a[i],i);
		ArrayList<Integer> tmp = new ArrayList<>();
		for(int i=0;i<b.length;++i) {
			if(map.containsKey(b[i]))
				tmp.add(map.get(b[i]));
		}
		//借助二分优化的LIS得到结果
		return LIS(tmp.stream().mapToInt(Integer::intValue).toArray());
	}

 3、最长公共子串问题

最长公共子串问题的描述为:给定两个字符串a和b,输出两个字符串的最长公共子串(最长公共子串存在且唯一)。

该问题与最长公共子序列问题属于同种类型,其区别仅在于一个是子串要求连续,另一个是子序列不要求连续。所以其状态表示与状态转移与LCS问题是几乎一样的,我们以f[i][j]表示以a中i位置结尾、b中j位置结尾的最长公共子串长度,对于状态f[i][j],其可能由以下决策转移而来:

 若a[i]=b[j],则a[i]、b[j]同时属于公共子串:

  • f[i][j]由f[i-1][j-1]转移而来,f[i][j]=f[i-1][j-1]+1;

若a[i]!=b[j],破坏了公共子串的连续性,则a[i]、b[j]都不属于公共子串:

  • f[i][j]=0;

综上,状态转移方程为:

gif.latex?f%5Bi%5D%5Bj%5D%3D%5Cleft%5C%7B%5Cbegin%7Bmatrix%7D%20f%5Bi-1%5D%5Bj-1%5D&plus;1%20%26%20a%5Bi%5D%3Db%5Bj%5D%5C%5C%200%20%26%20a%5Bi%5D%5Cneq%20b%5Bj%5D%20%5Cend%7Bmatrix%7D%5Cright.

    public String LCS (String s1, String s2) {
        int dp[][] = new int[s1.length()+1][s2.length()+1];
        int max = 0, ans = 0;
        for(int i=1;i<=s1.length();++i){
            for(int j=1;j<=s2.length();++j){
                if(s1.charAt(i-1)==s2.charAt(j-1))
                    dp[i][j]=dp[i-1][j-1]+1;
                else dp[i][j]=0;
                if(dp[i][j]>max){
                    max = dp[i][j];
                    ans = i;
                }
            }
        }
        return s1.substring(ans-max,ans);
    }

 

  • 2
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值