1. 什么是最长公共子序列?什么是最长公共子串?

1.1. 最长公共子序列(Longest-Common-Subsequences,LCS)

最长公共子序列(Longest-Common-Subsequences,LCS)是一个在一个序列集合中(通常为两个序列)用来查找所有序列中最长子序列的问题。这与查找最长公共子串的问题不同的地方是:子序列不需要在原序列中占用连续的位置

最长公共子序列问题是一个经典的计算机科学问题,也是数据比较程序,比如Diff工具,和生物信息学应用的基础。它也被广泛地应用在版本控制,比如Git用来调和文件之间的改变。

1.2 最长公共子串(Longest-Common-Substring,LCS)

最长公共子串(Longest-Common-Substring,LCS)问题是寻找两个或多个已知字符串最长的子串。此问题与最长公共子序列问题的区别在于子序列不必是连续的,而子串却必须是连续的。

2. 如何求解最长公共子序列?

例如序列str_a=world,str_b=wordl。序列wo是str_a和str_b的一个公共子序列,但是不是str_a和str_b的最长公共子序列,子序列word是str_a和str_b的一个LCS,序列worl也是。

暴力查找?
寻找LCS的一种方法是枚举X所有的子序列,然后注意检查是否是Y的子序列,并随时记录发现的最长子序列。假设X有m个元素,则X有2^m个子序列,指数级的时间,对长序列不实际。

分析问题,设str_a=<x1,x2,…,xm>和str_b=<y1,y2,…,yn>为两个序列,LCS(str_a,str_b)表示str_a和str_b的一个最长公共子序列,可以看出

如果str_a[m] == str_b[n],则LCS (str_a, str_b) = str_a[m] + LCS(str_a[1:m-1],str_b[1:n-1])

如果str_a[m] != str_b[n],则LCS(str_a,str_b)= max{LCS(str_a[1:m-1], str_b), LCS (str_a, str_b[n-1])}

LCS问题也具有重叠子问题性质:为找出LCS(str_a,str_b),可能需要找LCS(str_a[1:m-1], str_b)以及LCS (str_a, str_b[n-1])。但这两个子问题都包含着LCS(str_a[1:m-1],str_b[1:n-1]).

2.1 基于递归的方法

根据上边分析结果,可以写出简洁易懂的递归方法。

def recursive_lcs(str_a, str_b):
if len(str_a) == 0 or len(str_b) == 0:
return 0
if str_a[0] == str_b[0]:
return recursive_lcs(str_a[1:], str_b[1:]) + 1
else:
return max([recursive_lcs(str_a[1:], str_b), recursive_lcs(str_a, str_b[1:])])
print recursive_lcs(str_a, str_b)

2.2 基于自底向上动态规划的方法

根据上述分析问题,动态规划递推公式也非常明显,可以写出动态规划代码:

def bottom_up_dp_lcs(str_a, str_b):
"""
longest common subsequence of str_a and str_b
"""
if len(str_a) == 0 or len(str_b) == 0:
return 0
dp = [[0 for _ in range(len(str_b) + 1)] for _ in range(len(str_a) + 1)]
for i in range(1, len(str_a) + 1):
for j in range(1, len(str_b) + 1):
if str_a[i-1] == str_b[j-1]:
dp[i][j] = dp[i-1][j-1] + 1
else:
dp[i][j] = max([dp[i-1][j], dp[i][j-1]])
print "length of LCS is :",dp[len(str_a)][len(str_b)]
# 输出最长公共子序列
i, j = len(str_a), len(str_b)
LCS = ""
while i > 0 and j > 0:
if str_a[i-1] == str_b[j-1] \ # 这里一定要比较a[i-1]和b[j-1]是否相等
and dp[i][j] == dp[i-1][j-1] + 1:
LCS = str_a[i - 1] + LCS
i, j = i-1, j-1
continue
if dp[i][j] == dp[i-1][j]:
i, j = i-1, j
continue
if dp[i][j] == dp[i][j-1]:
i, j = i, j-1
continue
print "LCS is :", LCS
bottom_up_dp_lcs(str_a, str_b)

2.3 降低空间复杂度的动态规划算法

根据上述问题分析以及2.2中的dp矩阵可以看出,其实每一步的求解,只和三个元素有关:左边的元素,上边的元素,左上角的元素。因此我们可以进行空间优化,用一维数组代替二维矩阵。

def space_efficient_lcs(str_a, str_b):
"""
longest common subsequence of str_a and str_b, with O(n) space complexity
"""
if len(str_a) == 0 or len(str_b) == 0:
return 0
dp = [0 for _ in range(len(str_b) + 1)]
for i in range(1, len(str_a) + 1):
left_up = 0
dp[0] = 0
for j in range(1, len(str_b) + 1):
left = dp[j-1]
up = dp[j]
if str_a[i-1] == str_b[j-1]:
dp[j] = left_up + 1
else:
dp[j] = max([left, up])
left_up = up
print dp[len(str_b)]
space_efficient_lcs(str_a, str_b)

3. 如何求解最长公共子串?

最长公共子串比最长公共子序列的递推公式要简单一些。

dp[i][j]的含义也发生了变化:

  • 在最长公共子序列中,dp[i][j]表示str_a[1:i]和str_b[1:j]的最长公共子序列,是从str_a的1和str_b的1开始计算的,即整个字符串的起始位置。
  • 在最长公共子串中,dp[i][j]表示str_a[i':i]和str_b[j':j]的最长公共子串,因为str_a和str_b可能存在多个公共子串,所以i'和j'分别表示当前公共子串的起始位置。

也就是说:

  • 当str_a[i] == str_b[j]时,dp[i][j] = dp[i-1][j-1]+ 1;
  • 当str_a[i] != str_b[j]时,dp[i][j] = 0,即开始计算新的公共子串。

和最长公共子序列不同的是,在最长公共子串问题中,dp[m][n]不一定是最终结果,比如“abcdxy”和“abcfxy”,dp[m][n]存储的是公共子串“xy”的长度,而不是公共子串“abc”的长度,所以需要一个变量单独记录最长子串的长度。

3.1 动态规划算法

def bottom_up_dp_lcs(str_a, str_b):
"""
longest common substring of str_a and str_b
"""
if len(str_a) == 0 or len(str_b) == 0:
return 0
dp = [[0 for _ in range(len(str_b) + 1)] for _ in range(len(str_a) + 1)]
max_len = 0
lcs_str = ""
for i in range(1, len(str_a) + 1):
for j in range(1, len(str_b) + 1):
if str_a[i-1] == str_b[j-1]:
dp[i][j] = dp[i-1][j-1] + 1
max_len = max([max_len, dp[i][j]])
if max_len == dp[i][j]:
lcs_str = str_a[i-max_len:i]
else:
dp[i][j] = 0
print "length of LCS is :",max_len
print "LCS :",lcs_str
bottom_up_dp_lcs(str_a, str_b)

3.2 优化空间复杂度的动态规划算法

def space_efficient_lcs(str_a, str_b):
"""
longest common substring of str_a and str_b, with O(n) space complexity
"""
if len(str_a) == 0 or len(str_b) == 0:
return 0
max_len = 0
dp = [0 for _ in range(len(str_b) + 1)]
for i in range(1, len(str_a) + 1):
left_up = 0
for j in range(1, len(str_b) + 1):
up = dp[j]
if str_a[i-1] == str_b[j-1]:
dp[j] = left_up + 1
max_len = max([max_len, dp[j]])
else:
dp[j] = 0
left_up = up
print max_len
space_efficient_lcs(str_a, str_b)

【ZH奶酪】如何用Python计算最长公共子序列和最长公共子串的更多相关文章

  1. 用Python计算最长公共子序列和最长公共子串

    如何用Python计算最长公共子序列和最长公共子串 1. 什么是最长公共子序列?什么是最长公共子串? 1.1. 最长公共子序列(Longest-Common-Subsequences,LCS) 最长公 ...

  2. 动态规划1——最长递增子序列、最长公共子序列、最长公共子串(python实现)

    目录 1. 最长递增序列 2. 最长公共子序列 3. 最长公共子串 1. 最长递增序列 给定一个序列,找出其中最长的,严格递增的子序列的长度(不要求连续). 解法一:动态规划 通过一个辅助数组记录每一 ...

  3. [Python]最长公共子序列 VS 最长公共子串[动态规划]

    前言 由于原微软开源的基于古老的perl语言的Rouge依赖环境实在难以搭建,遂跟着Rouge论文的描述自行实现. Rouge存在N.L.S.W.SU等几大子评估指标.在复现Rouge-L的函数时,便 ...

  4. [Data Structure] LCSs——最长公共子序列和最长公共子串

    1. 什么是 LCSs? 什么是 LCSs? 好多博友看到这几个字母可能比较困惑,因为这是我自己对两个常见问题的统称,它们分别为最长公共子序列问题(Longest-Common-Subsequence ...

  5. 动态规划 最长公共子序列 LCS,最长单独递增子序列,最长公共子串

    LCS:给出两个序列S1和S2,求出的这两个序列的最大公共部分S3就是就是S1和S2的最长公共子序列了.公共部分 必须是以相同的顺序出现,但是不必要是连续的. 选出最长公共子序列.对于长度为n的序列, ...

  6. 最长公共子序列与最长公共字串 (dp)转载http://blog.csdn.net/u012102306/article/details/53184446

    1. 问题描述 子串应该比较好理解,至于什么是子序列,这里给出一个例子:有两个母串 cnblogs belong 比如序列bo, bg, lg在母串cnblogs与belong中都出现过并且出现顺序与 ...

  7. O(n log n)求最长上升子序列与最长不下降子序列

    考虑dp(i)表示新上升子序列第i位数值的最小值.由于dp数组是单调的,所以对于每一个数,我们可以二分出它在dp数组中的位置,然后更新就可以了,最终的答案就是dp数组中第一个出现正无穷的位置. 代码非 ...

  8. 最长公共子序列PK最长公共子串

    1.先科普下最长公共子序列 & 最长公共子串的区别: 找两个字符串的最长公共子串,这个子串要求在原字符串中是连续的.而最长公共子序列则并不要求连续. (1)递归方法求最长公共子序列的长度 1) ...

  9. 动态规划(一)——最长公共子序列和最长公共子串

    注: 最长公共子序列采用动态规划解决,由于子问题重叠,故采用数组缓存结果,保存最佳取值方向.输出结果时,则自顶向下建立二叉树,自底向上输出,则这过程中没有分叉路,结果唯一. 最长公共子串采用参考串方式 ...

随机推荐

  1. hdu1890 splay维护区间翻转

    这题的建模有点不太一样,是按结点横坐标赋予键值的 同时每次rotate和splay时都要注意下往上往下更新 /* 先建立好splay tree,将结点按num/输入顺序排序,遍历时每次将当前结点提到根 ...

  2. SPLAY,LCT学习笔记(五)

    这一篇重点探讨LCT的应用 例:bzoj 2631 tree2(国家集训队) LCT模板操作之一,利用SPLAY可以进行区间操作这一性质对维护懒惰标记,注意标记下传顺序和如何下传 #include & ...

  3. 基于TCPCopy的仿真压测方案

    一.tcpcopy工具介绍 tcpcopy 是一个分布式在线压力测试工具,可以将线上流量拷贝到测试机器,实时的模拟线上环境,达到在程序不上线的情况下实时承担线上流量的效果,尽早发现 bug,增加上线信 ...

  4. hdu 1217 汇率 Floyd

    题意:给几个国家,然后给这些国家之间的汇率.判断能否通过这些汇率差进行套利交易. Floyd的算法可以求出任意两点间的最短路径,最后比较本国与本国的汇率差,如果大于1,则可以.否则不可以. 有向图 一 ...

  5. window 下忘记了mysql 密码的解决方法

    1.以管理员身份打开cmd,关闭MySQL. net stop mysql 2.跳过权限检查启动,进入安装目录bin下. mysqld --skip-grant-tables或者mysqld-nt - ...

  6. [转] 通过Ajax方式上传文件,使用FormData进行Ajax请求

    通过传统的form表单提交的方式上传文件: <form id= "uploadForm" action= "http://localhost:8080/cfJAX_ ...

  7. [转] Java接口_interface_implements

    相对抽象类来讲,接口就是比抽象类还要抽象的抽象类,丝毫不带半点实现的内容.接口可以更加规范的对子类进行约束.接口全面地专业地实现了:规范和具体实现的分离.接口就是规范,定义的是一组规则,提现了现实世界 ...

  8. 支持删除的并查集 hdu2473

    题解: 代码: #include<bits/stdc++.h> using namespace std; #define ll long long ; int fa[maxn],id,vi ...

  9. [转]硬盘的那些事(主分区、扩展分区、逻辑分区、活动分区、系统分区、启动分区、引导扇区、MBR等)

    http://xu3stones.blog.163.com/blog/static/205957136201210309424303 主分区,扩展分区,逻辑分区,活动分区,系统分区,启动分区..... ...

  10. 项目的整体框架,以及Topology的设计

    一:说明 1.项目的整体框架 2.Topology的设计 3.记录 0. 89.201.10.122 - - [1528033390201] "GET /edit.php HTTP/1.1& ...