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. HRBUST - 1818 石子合并 区间dp入门

    有点理解了进阶指南上说的”阶段,状态和决策“ /* 区间dp的基础题: 以区间长度[2,n]为阶段,枚举该长度的区间,状态dp[l][r]表示合并区间[l,r]的最小费用 状态转移方程dp[l][r] ...

  2. poj1015 01二维背包

    /* 给定辩控双方给每个人的打分p[i],d[i], dp[j][k]表示前i个人有j个被选定,选定的人的辩控双方打分差之和是k,此状态下的最大辩控双方和 按01背包做,体积一维是1,体积二维是辩控双 ...

  3. python 全栈开发,Day11(函数名应用,闭包,装饰器初识,带参数以及带返回值的装饰器)

    一.函数名应用 函数名是什么?函数名是函数的名字,本质:变量,特殊的变量. 函数名(),执行此函数. python 规范写法 1. #后面加一个空格,再写内容,就没有波浪线了. 2.一行代码写完,下面 ...

  4. 解决Oracle出现以0开头的小数,开头的0消失的问题

    项目中碰到了个问题,本来报表需要显示“0.49%”,结果就是显示成“.49%” 找问题 首先在pl/sql工具里执行sql,发现原始的数据就是“.49%”,那么问题来了,原始sql的问题,跟工具无关了 ...

  5. C#之app.config、exe.config和vshost.exe.config作用区别

    vshost.exe.config是程序运行时的配置文本 exe.config是程序运行后会复制到vshost.exe.config app.config是在vshost.exe.config和exe ...

  6. poj 3415

    对拍没错..莫名wa了 利用容斥求每个串的重复子串 其实就是找到每个元素能扩展到的最大元素 即(rr-i)*(i-lr)*(w[i]-kk) 就可以了 然后处理这个先离散化再搞 另外是x y要清空 # ...

  7. 【Java】 剑指offer(21) 调整数组顺序使奇数位于偶数前面

    本文参考自<剑指offer>一书,代码采用Java语言. 更多:<剑指Offer>Java实现合集   题目 输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇 ...

  8. 既有e^x又有sinx或cosx的积分题的解法

    楼上三位,一致对e^x情有独钟,他们都是对的.通常,这类题既有e^x又有sinx或cosx的积分题,一般的解法是:1.选定e^x,或选定sinx.cosx,就得“从一而终”,用分部积分的方法计算,   ...

  9. python连接mysql、sqlserver、oracle、postgresql数据库的一些封装

    包括python连接数据库,以及django下配置连接数据库 # -*- coding:utf-8 -*- import psycopg2 import pymysql import pymssql ...

  10. 观察者模式之ES6实现(一)

    一.参考链接 https://github.com/JacksonTian/eventproxy/tree/master/lib 二.代码实现 // eventProxy.js 'use strict ...