前面两篇我们讲解了01背包问题和最少硬币找零问题。这篇将介绍另一个经典的动态规划问题--最长公共子序列。如果没看过前两篇,可点击下面链接。

详解动态规划最少硬币找零问题--JavaScript实现

详解动态规划01背包问题--JavaScript实现

问题

给定两个字符串序列 abcadf , acbad,求这两个字符串的最长公共子序列

分析

最长公共子序列问题,有三个点需要注意

  • 两个序列长度不一定相同
  • 最长子序列是指,在两个字符串序列中以相同顺序出现
  • 所求的子序列不需要连续

在进行填表分析之前,根据上面提到的三个点,我们可以很容易地先直接得出答案,最长公共子序列应为 acad

1. 建表

我们给两个子序列前面都加一个空字符,即

input1 = ["","a","c","b","a","d"],
input2 = ["","a","b","c","a","d","f"],复制代码

然后构建如下表格

为什么填一堆0呢?表示字符串无法匹配,你可以理解这是一种辅助的计算方式,在分析具体子序列时,不把构建的空字符纳入考虑范围。在后面也会按照前面2篇的思路,使用T[i][j]表示组合的子序列长度。

下面将从左往右,从上往下开始填表。我们在填写某一个表格的时候,只需要考虑小于等于i 和小于等于j的情况。比如我们要填写T[2][2]时,那么此时等同于求字符串 ac,ab的最长公共子序列,填写T[4][5]时,那么此时等同于求 acbaabcad的最长公共子序列长度。

如果你看过前两篇,对于这种填表应该会很熟悉。 下面基于这个表格,开始填表。

2. i =1

我们从第一行开始。

i=1 j=1:此时等同于求字符串 aa的最长公共子序列长度,很显然结果为1。

i=1 j=2:此时等同于求字符串 aab的最长公共子序列长度,结果为1。

i=1 j=3:此时等同于求字符串 aabc的最长公共子序列长度,结果为1。

只要一个序列只有一个字符,那么另一个序列无论多长,它们的最长公共子序列长度最多只能为1。所以 i=1 行剩余空格都填1。

3. i = 2

i=2 j=1:此时等同于求字符串 aca的最长公共子序列长度,结果为1。

i=2 j=2:此时等同于求字符串 acab的最长公共子序列长度,结果为1。

i=2 j=3:此时等同于求字符串 acabc的最长公共子序列长度。这时就有意思了。因为根据一开始的分析,求最长公共子序列时,子序列是可以不连续的,因此这两个序列的最长公共子序列应该是 ac,所以这里表格应该填2。

好了,停下,先不用急着继续填,我们需要先分析一下通用思路。

4.填表思路

我们从T[2][3]=2 这一个格分析。很显然去除 c 这个公共字符后,两个字符串还剩下 a, ab。是不是有点熟悉?这个其实就是填写 T[1][2] 时的组合,也就是我们可以假设当 input1[i] == input2[j]时,T[i][j]=T[i-1][j-1]+1。 当input1[i] != input2[j]时,T[i][j]的值,取它上方或左边的较大值,即[i][j] = max(T[i-1][j],T[i][j-1])

用一句通俗的话来描述这种T[i][j]规律,就是相等左上角加一,不等取上或左最大值,如果上左一样大,优先取左。

好了,不看下面内容,你带着这种规律,把表格剩余内容自己填写完毕。

5.最终表格

理解了这种规律,我们没必要把每一格该怎么填重复叙述了。下面就是最终表格。

我们举个例子,比如 i=5 j=4,此时input1[i] !=input2[j],我们取它左边(2)或者上方(3)的较大值,所以填写3。

i=5 j=5,此时input1[i] ==input2[j],我们直接取左上角值加1,左上角的值为T[4][4]=3,所以T[5][5]=4 。

如果还不太理解,可以自己再练习画一次。

6.寻找子串

我们完成填表后,只能求出最长公共子序列的长度,但是无法得知它的具体构成。我们可以参照上一篇硬币问题,从填表的反向角度来寻找子序列。

我们子序列保存在名为 s的数组中,从表格中反向搜索,找到目标字符后,每次都把目标字符插入到数组最前面。

根据前面提供的填表口诀,我们可以反向得出寻找子序列的口诀: 如果T[i][j]来自左上角加一,则是子序列,否则向左或上回退。如果上左一样大,优先取左。

1. 从右下角开始分析,T[5][6]=4,它并不是来自左上角。它左边的值比上方大,所以它来自左边,向左回退,如下图箭头。

2. 接着就定位到 T[5][5],显然他来自左上角加1,它是子序列。插入数组中,有

s = ['d']
复制代码

3. 扣除掉 T[5][5],可以定位到它的左上角 T[4][4],如图:

T[4][4]也是来自左上角加1,它也是子序列,把它插入到数组最前面,此时 s 应该是

s = ['a','d']
复制代码

4. 按照前面的思路,继续定位分析,最终如下图:

最终箭头指向0,搜索结束。

s = ['a','b','a','d']
复制代码

伪代码

整个分析过程已经完成了。下面提供代码逻辑,即使不懂 JavaScript,也不会影响你理解,因为没有涉及语言特性。

填表

if(input1[i] == input2[j]){T[i][j] = T[i-1][j-1] + 1;
}else{T[i][j] = max(T[i-1][j],T[i][j-1])
}
复制代码

寻找子串

if(input1[i] == input2[j]){s.insertToIndexZero(input1[i]); //插入到数组最前面i--;j--;
}else{//向左或向上回退if(T[i-1][j]>T[i][j-1]){//向上回退i--;}else{//向左回退j--;}
}
复制代码

完整代码

最终代码使用 JavaScript 实现,如果你的 Sublime 支持纯 JavaScript,你可以直接复制黏贴代码,command + b 直接运行查看结果,然后修改输入变量,查看更多情况下的输出结果。

//动态规划 -- 最长公共子序列//!!!!  T[i][j] 计算,记住口诀:相等左上角加一,不等取上或左最大值function longestSeq(input1,input2,n1,n2){var T = []; // T[i][j]表示 公共子序列长度for(let i=0;i<n1;i++){T[i] = [];for(let j= 0;j<n2;j++){if(j==0 ||i==0){T[i][j] = 0;continue;}if(input1[i] == input2[j]){T[i][j] = T[i-1][j-1] + 1;}else{T[i][j] = Math.max(T[i-1][j],T[i][j-1])}}}findValue(input1,input2,n1,n2,T);return T;}//!!!如果它来自左上角加一,则是子序列,否则向左或上回退。
//findValue过程,其实就是和 就是把T[i][j]的计算反过来。
function findValue(input1,input2,n1,n2,T){var i = n1-1,j=n2-1;var result = [];//结果保存在数组中console.log(i);console.log(j);while(i>0 && j>0){if(input1[i] == input2[j]){result.unshift(input1[i]);i--;j--;}else{//向左或向上回退if(T[i-1][j]>T[i][j-1]){//向上回退i--;}else{//向左回退j--;}}}console.log(result);
}//两个序列,长度不一定相等, 从计算表格考虑,把input1和input2首位都补一个用于占位的空字符串
var input2 = ["","a","b","c","a","d","f"],input1 = ["","a","c","b","a","d"],n1 = input1.length,n2 = input2.length;console.log(longestSeq(input1,input2,n1,n2));
复制代码

详解动态规划最长公共子序列--JavaScript实现相关推荐

  1. 动态规划1--最长公共子序列

    动态规划1--最长公共子序列 一.动态规划 经常会遇到复杂问题不能简单地分解成几个子问题,而会分解出一系列的子问题.简单地采用把大问题分解成子问题,并 综合子问题的解导出大问题的解的方法,问题求解耗时 ...

  2. 动态规划—最长公共子序列问题 HDU-1159 Common Subsequence

    动态规划-最长公共子序列问题 Common Subsequence [ HDU - 1159 ] A subsequence of a given sequence is the given sequ ...

  3. 最长公共子序列php,动态规划(最长公共子序列LCS)

    概念 求解决策过程最优化的结果 (可能有多个) 把多阶段过程转化为一系列单阶段过程,利用各阶段之间的关系,逐个求解 计算过程中会把结果都记录下,最终结果在记录中找到. 举例 求两个字符串的最长公共子序 ...

  4. 最长公共子序列动态规划c语言,动态规划----最长公共子序列(C++实现)

    最长公共子序列 题目描述:给定两个字符串s1 s2 - sn和t1 t2 - tm .求出这两个字符串的最长公共子序列的长度.字符串s1 s2 - sn的子序列指可以表示为 - { i1 < i ...

  5. 算法导论之动态规划(最长公共子序列和最优二叉查找树)

    动态规划师通过组合子问题的解而解决整个问题,将问题划分成子问题,递归地求解各子问题,然后合并子问题的解而得到原问题的解.和分治算法思想一致,不同的是分治算法适合独立的子问题,而对于非独立的子问题,即各 ...

  6. 【啃不完的算法导论】- 动态规划 - 最长公共子序列(概念篇)

    以下内容纯是为了熟悉<算法导论>中的内容,高手可略过,其中涉及的书本内容的版权归原作者.译者.出版社所有 ========================================= ...

  7. 动态规划—最长公共子序列LCS及模板

    一,问题描述 给定两个字符串,求解这两个字符串的最长公共子序列(Longest Common Sequence).比如字符串1:BDCABA:字符串2:ABCBDAB.这两个字符串的最长公共子序列长度 ...

  8. 动态规划 最长公共子序列 过程图解

    长公共子序列(longest common sequence) 最长公共子串(longest common substring) 子序列:一个给定的序列的子序列,将给定序列中零个或多个元素去掉之后得到 ...

  9. 动态规划----最长公共子序列问题

    动态规划算法与分治法类似,其基本思想也是将待求解的问题分解为若干个子问题,先求解子问题,然后再从若干个子问题的解得到原问题的解.与分治法不同的是,适合于用动态规划法求解的问题,经分解得到的子规模往往不 ...

最新文章

  1. android 绘画,Android绘图基础
  2. 艺术站-卡通和风格化的HDRI天空
  3. 做动态图表没有数据?用Python就能获取
  4. 判断CPU是大端还是小端
  5. php中数组生成下拉选项,php利用数组填充下拉列表框
  6. Android官方开发文档Training系列课程中文版:布局性能优化之布局复用
  7. Hadoop HIVE 基本函数
  8. Jquery实现定时器实例
  9. 麒麟9000芯片的库存真就是个迷
  10. 一道『easy』等级的力扣题,我写了两个小时的笔记...
  11. [WPF系列]Adorner应用-自定义控件ImageHotSpot
  12. win10下装黑苹果双系统_手把手教你轻松安装 Win10/ 黑苹果macOS10.14.1双系统
  13. pi控制直流电机c语言,一种基于PI控制的直流电机调速控制系统及控制方法与流程...
  14. VISTA 服务详解
  15. 数据库管理-第二十九期 记一次AFD环境的存储变更(20220803)
  16. ireport 循环_ireport detail循环原理
  17. windows10下F1-F11快捷键及window+Dor+E快捷键打开关闭控制
  18. Google Play上架总结(一)为什么要上架Google Play
  19. 心跳之旅——iOS用手机摄像头检测心率(PPG)
  20. Android8.0 安装apk

热门文章

  1. 易语言怎么判断文件是否一样_怎么判断网站建设公司是否正规
  2. oracle三种分区的方式,Oracle 分区表 总结大全(3)
  3. 查看计算机用户创建时间,敬业签在电脑端怎样查看团签内容的创建时间?
  4. php动态数组的存储过程,PHP数组作为存储过程的输入
  5. ftl不存在为真_LTL和FTL货运之间有什么区别?
  6. python多线程代码_Python多线程代码求改错
  7. HDLBits 系列(4)如何设计一定不会产生Latch的组合逻辑?
  8. 【Verilog HDL 训练】第 06 天(边沿检测)
  9. 【 MATLAB 】如何产生一个均值和方差可控的正态分布矩阵(randn)?
  10. delete from t引发的血案