64、最长公共子序列-II(不连续)

一、描述

给定两个字符串str1和str2,输出两个字符串的最长公共子序列。如果最长公共子序列为空,则返回"-1"。目前给出的数据,仅仅会存在一个最长的公共子序列

二、最长公共子序列和最长公共子串的区别

  1. 子序列:即一个给定的序列的子序列,就是将给定序列中零个或多个元素去掉之后得到的结果
  2. 子串:给定串中任意个连续的字符组成的子序列称为该串的子串。

给一个图再解释一下:

  • 子序列: {a,b,c,d,e,f,g,h},它的子序列示例: {a,c,e,f} 即元素b,d,g,h被去掉后,保持原有的元素序列所得到的结果就是子序列。同理,{a,h},{c,d,e}等都是它的子序列。
  • 子串:{c,d,e,f} 即连续元素c,d,e,f组成的串是给定序列的子串。同理,{a,b,c,d},{g,h}等都是它的子串。
     

三、求最长公共子序列(LCS)的思路:动态规划

(1)递归公式:进行动态规划,构造LCS数组

  • c[i,j]表示Xi 和 Yj 的LCS的长度;
  • 其中X = {x1 ... xm},Y ={y1...yn},Xi = {x1 ... xi},Yj={y1... yj};
  1. 若Xi和Yj的最后一个元素相等,那么Xi和Yj的LCS就等于 {Xi减去最后一个元素} 与 {Yj减去最后一个元素} 的 LCS  再加上 S1和S2相等的最后一个元素。即C[i-1][j-1]+1;
  2. 若Xi的最后一个元素 与Yj的最后一个元素不等,那么Xi和Yj的LCS就等于 : {Xi减去最后一个元素} 与 Yj的LCS, {Yj减去最后一个元素} 与 Xi 的LCS 中的最大的那个序列。即max{C[i,j-1], C[i-1][j]}

 (2)根据LCS数组反推,构造最长公共子序列

以的s1={1,3,4,5,6,7,7,8},s2={3,5,7,4,8,6,7,8,2} 的LCS数组为例:

注意:本文S1和S2的最LCS并不是只有1个,本文并不是着重讲输出两个序列的所有LCS,只是介绍如何通过上表,输出其中一个LCS。 

2.1)第一种结果:

我们将从最后一个元素c[8][9]倒推出S1和S2的LCS:

  1. c[8][9] = 5,且S1[8] != S2[9],所以倒推回去,c[8][9]的值来源于c[8][8]的值(因为c[8][8] > c[7][9])。
  2. c[8][8] = 5,  且S1[8] = S2[8], 所以倒推回去,c[8][8]的值来源于 c[7][7]。
  3. 以此类推,如果遇到S1[i] != S2[j] ,且c[i-1][j] = c[i][j-1] 这种存在分支的情况,这里请都选择一个方向(之后遇到这样的情况,也选择相同的方向)。

    第一种结果为:

 这就是倒推回去的路径,棕色方格为相等元素,即LCS = {3,4,6,7,8}

 2.2)第二种结果:

如果遇到S1[i] != S2[j] ,且c[i-1][j] = c[i][j-1] 这种存在分支的情况,选择另一个方向,会得到另一个结果,即LCS ={3,5,7,7,8}。

 

四、实现

/**
 * longest common subsequence
 * @param s1 string字符串 the string
 * @param s2 string字符串 the string
 * @return string字符串
 */
function LCS( s1 ,  s2 ) {
    // write code here
    //动态规划,构造LCS数组。 C[i][j] 表示串s1i和串s2j的最长公共子序列长度
    //反推结果,获取最长的公共子序列。
    let len1 = s1.length;
    let len2 = s2.length;
    if(len1==0 || len2==0){
        return -1;
    }
    let dp = [];
    for(let i=0;i<len1+1;i++){
        dp[i] = [];
        for(let j=0;j<len2+1;j++){
            dp[i][j] = 0;
        }
    }
    //1. 动态规划,构造LCS数组
    for(let i=0;i<len1+1;i++){
        for(let j=0;j<len2+1;j++){
            //1.1  串的长度为0
            if(i==0 || j==0){
                dp[i][j] = 0;
                continue;
            }
            //1.2 两串的最后一个字符相等
            if(s1[i-1] === s2[j-1]){ //第一个字符对应下标1,以此类推
                dp[i][j] = dp[i-1][j-1]+1;
            }else{ //1.3 两串的最后一个字符不相等
                dp[i][j] = Math.max(dp[i-1][j],dp[i][j-1])
            }
        }
    }
    //console.log(dp)
    //2. 根据dp反推LCS,从后向前推
    let m1 = len1, m2 = len2;
    let res = [];
    while(m1>0 && m2>0){
        if(s1[m1-1] == s2[m2-1]){ //2.1 两字符相等:s1[m1] = s2[m2]
            res.unshift(s1[m1-1])
            m1--;
            m2--;
        }else{//2.2 不相等,取子串的最长公共子序列中,最大的
            if(dp[m1-1][m2]>dp[m1][m2-1]){
                m1--;
            }else{
                m2--;
            }
        }
    }
    if(!res.length){
        return -1;
    }
    return res.join("")
}
module.exports = {
    LCS : LCS
};

 

时间复杂度:

构建c[i][j]表需要Θ(mn),输出1个LCS的序列需要Θ(m+n)

参考:(25条消息) 动态规划 最长公共子序列 过程图解_hrn1216的博客-CSDN博客_最长公共子序列 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值