ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

最长公共子序列LCS(dp问题)

2021-01-15 20:29:38  阅读:109  来源: 互联网

标签:字符 LCS else comeFrom let result 序列 dp


本文转载自:进击的大前端《前端也能学算法:由浅入深讲解动态规划》–蒋鹏飞 Dennis的部分内容
在这里插入图片描述

一、分析

上述问题也可以用暴力穷举来求解,先列举出X字符串所有的子串,假设他的长度为m,则总共有种情况,因为对于X字符串中的每个字符都有留着和不留两种状态,m个字符的全排列种类就是种。那对应的Y字符串就有种子串, n为Y的长度。然后再遍历找出最长的公共子序列,这个复杂度非常高,我这里就不写了。

我们观察两个字符串,如果他们最后一个字符相同,则他们的LCS(最长公共子序列简写)就是两个字符串都去掉最后一个字符的LCS再加一。因为最后一个字符相同,所以最后一个字符是他们的子序列,把他去掉,子序列就少了一个,所以他们的LCS是他们去掉最后一个字符的字符串的LCS再加一。如果他们最后一个字符不相同,那他们的LCS就是X去掉最后一个字符与Y的LCS,或者是X与Y去掉最后一个字符的LCS,是他们两个中较长的那一个。写成数学公式就是:
在这里插入图片描述
看着这个公式,一个规模为(i, j)的问题转化为了规模为(i-1, j-1)的问题,这不就又可以用递归求解了吗?

二、代码

动态规划
递归虽然能实现我们的需求,但是复杂度实在太高,长一点的字符串需要的时间是指数级增长的。我们还是要用动态规划来求解,根据我们前面讲的动态规划原理,我们需要从小的往大的算,每算出一个值都要记下来。因为c(i, j)里面有两个变量,我们需要一个二维数组才能存下来。注意这个二维数组的行数是X的长度加一,列数是Y的长度加一,因为第一行和第一列表示X或者Y为空串的情况。代码如下:

function lcs2(str1, str2) {
  let length1 = str1.length;
  let length2 = str2.length;
  
  // 构建一个二维数组
  // i表示行号,对应length1 + 1
  // j表示列号, 对应length2 + 1
  // 第一行和第一列全部为0
  let result = [];
  for(let i = 0; i < length1 + 1; i++){
    result.push([]); //初始化每行为空数组
    for(let j = 0; j < length2 + 1; j++){
      if(i === 0) {
        result[i][j] = 0; // 第一行全部为0
      } else if(j === 0) {
        result[i][j] = 0; // 第一列全部为0
      } else if(str1[i - 1] === str2[j - 1]){
        // 最后一个字符相同
        result[i][j] = result[i - 1][j - 1] + 1;
      } else{
        // 最后一个字符不同
        result[i][j] = result[i][j - 1] > result[i - 1][j] ? result[i][j - 1] : result[i - 1][j];
      }
    }
  }
  
  console.log(result);
  return result[length1][length2]
}

let result = lcs2('ABCBDAB', 'BDCABA');
console.log(result);   // 4

上面的result就是我们构造出来的二维数组,对应的表格如下,每一格的值就是c(i, j),如果,则它的值就是他斜上方的值加一,如果,则它的值是上方或者左方较大的那一个。

在这里插入图片描述

输出最长公共子序列
要输出LCS,思路还是跟前面切钢条的类似,把每一步操作都记录下来,然后再回溯。为了记录操作我们需要一个跟result二维数组一样大的二维数组,每个格子里面的值是当前值是从哪里来的,当然,第一行和第一列仍然是0。每个格子的值要么从斜上方来,要么上方,要么左方,所以:

1. 我们用1来表示当前值从斜上方来
2. 我们用2表示当前值从左方来
3. 我们用3表示当前值从上方来
function lcs3(str1, str2) {
  let length1 = str1.length;
  let length2 = str2.length;
  
  // 构建一个二维数组
  // i表示行号,对应length1 + 1
  // j表示列号, 对应length2 + 1
  // 第一行和第一列全部为0
  let result = [];
  let comeFrom = [];   // 保存来历的数组
  for(let i = 0; i < length1 + 1; i++){
    result.push([]); //初始化每行为空数组
    comeFrom.push([]);
    for(let j = 0; j < length2 + 1; j++){
      if(i === 0) {
        result[i][j] = 0; // 第一行全部为0
        comeFrom[i][j] = 0;
      } else if(j === 0) {
        result[i][j] = 0; // 第一列全部为0
        comeFrom[i][j] = 0;
      } else if(str1[i - 1] === str2[j - 1]){
        // 最后一个字符相同
        result[i][j] = result[i - 1][j - 1] + 1;
        comeFrom[i][j] = 1;      // 值从斜上方来
      } else if(result[i][j - 1] > result[i - 1][j]){
        // 最后一个字符不同,值是左边的大
        result[i][j] = result[i][j - 1];
        comeFrom[i][j] = 2;
      } else {
        // 最后一个字符不同,值是上边的大
        result[i][j] = result[i - 1][j];
        comeFrom[i][j] = 3;
      }
    }
  }
  
  console.log(result);
  console.log(comeFrom);
  
  // 回溯comeFrom数组,找出LCS
  let pointerI = length1;
  let pointerJ = length2;
  let lcsArr = [];   // 一个数组保存LCS结果
  while(pointerI > 0 && pointerJ > 0) {
    console.log(pointerI, pointerJ);
    if(comeFrom[pointerI][pointerJ] === 1) {
      lcsArr.push(str1[pointerI - 1]);
      pointerI--;
      pointerJ--;
    } else if(comeFrom[pointerI][pointerJ] === 2) {
      pointerI--;
    } else if(comeFrom[pointerI][pointerJ] === 3) {
      pointerJ--;
    }
  }
  
  console.log(lcsArr);   // ["B", "A", "D", "B"]
  //现在lcsArr顺序是反的
  lcsArr = lcsArr.reverse();
  
  return {
    length: result[length1][length2], 
    lcs: lcsArr.join('')
  }
}

let result = lcs3('ABCBDAB', 'BDCABA');
console.log(result);   // {length: 4, lcs: "BDAB"}

标签:字符,LCS,else,comeFrom,let,result,序列,dp
来源: https://blog.csdn.net/wenrenfudi/article/details/112687241

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有