1. 两者区别

约定:在本文中用 LCStr 表示最长公共子串(Longest Common Substring),LCSeq 表示最长公共子序列(Longest Common Subsequence)。

子串要求在原字符串中是连续的,而子序列则没有要求。例如:

字符串 s1=abcde,s2=ade,则 LCStr=de,LCSeq=ade。

2. 求最长公共子串(LCStr)

算法描述:构建如下图的矩阵dp[][],当s1[i] == s2[j] 的时候,dp[i][j]=1;最后矩阵中斜对角线上最长的“1”序列的长度,就是 LCStr 的长度。

但是求矩阵里斜线上的最长的“1”序列,仍然略显麻烦,我们进行如下优化:当要往矩阵中填“1”的时候,我们不直接填“1”,而是填“1”+左上角的那个数。如下图所示:

这样,我们只需求出矩阵里的最大数(注意:最大数可不一定在最右下角,别误解了上图),即是 LCStr 的长度。

要求出这个 LCStr,其他的不多说了,见代码中注释。

C++ code:

 #include <iostream>
#include <string>
#include <cstdlib> // freopen
#include <cstring> // memset
using namespace std; #define MAXN 2001
static int dp[MAXN][MAXN]; string LCStr(const string &s1, const string &s2)
{
string result; //s1纵向,s2横向
//len1行,len2列
int len1=s1.length(), len2=s2.length();
memset(dp,,sizeof(dp)); //预先处理第一行第一列
for(int i=; i<len2; ++i)
if(s1[]==s2[i]) dp[][i]=;
for(int i=; i<len1; ++i)
if(s1[i]==s2[]) dp[i][]=; for(int i=; i<len1; ++i)
for(int j=; j<len2; ++j)
if(s1[i]==s2[j]) dp[i][j]=dp[i-][j-]+; //矩阵填充 //将第一行的最大值移到最右边
for(int i=; i<len2; ++i)
if(dp[][i]<dp[][i-]) dp[][i]=dp[][i-]; //从第二行开始,将每一行的最大值移到最右边
//最后边的数和上一行的最右边数比较大小,将大的下移
//到最后,右下角的数就是整个矩阵的最大值
for(int i=; i<len1; ++i)
{
for(int j=; j<len2; ++j)
if(dp[i][j]<dp[i][j-]) dp[i][j]=dp[i][j-];
if(dp[i][len2-]<dp[i-][len2-]) dp[i][len2-]=dp[i-][len2-];
}
cout<<"length of LCStr: "<<dp[len1-][len2-]<<endl; int max = dp[len1-][len2-];
int pos_x;
for(int i=; i<len1; ++i)
for(int j=; j<len2; ++j)
{
if(dp[i][j]==max)
{
pos_x=i;
j=len2; ///
i=len1; ///快速跳出循环
}
}
result=s1.substr(pos_x-max+,max);
return result;
} int main()
{
int t;
freopen("in.txt","r",stdin);
cin>>t;
cout<<"total tests: "<<t<<endl<<endl;
while(t--)
{
string a,b;
cin>>a>>b;
cout<<a<<endl<<b<<endl; string res=LCStr(a,b);
cout<<"LCStr: "<<res<<endl<<endl;
}
return ;
}

运行:

输入:

5
abcde
ade
flymouseEnglishpoor
comeonflymouseinenglish
BCXCADFESBABCACA
ABCACADF
programming
contest
123454567811267234678392
1457890567809713265738

输出:

3. 最长公共子序列(LCSeq)

算法描述:

矩阵最后的 dp[i][j] 就是 LCSeq 的长度。

为了把 LCSeq 求出来,我们在给每一个 dp[i][j] 赋值的时候,需要记住这个值来自于哪里。是来自于左上角(LEFTUP),还是上边(UP),还是左边(LEFT)。然后从矩阵最后一个元素回溯,就能找出 LCSeq。如下图:

当 dp[i-1][j]==dp[i][j-1],即左边的元素等于上边的元素时,我取上边的元素。(取左边的也行,并不影响程序结果。但在整个代码中要统一规则)。

C++ code:

 #include <iostream>
#include <string>
#include <cstring> //memset
#include <algorithm> //reverse
#define LEFTUP 0
#define UP 1
#define LEFT 2
#define MAXN 2001
using namespace std; //s1纵向,s2横向
int dp[MAXN][MAXN];
short path[MAXN][MAXN];
string LCSeq(const string &s1, const string &s2)
{
int len1=s1.length(), len2=s2.length();
string result=""; //将dp[][]和path[][]的首行首列清零
for(int j=; j<=len2; ++j)
{dp[][j]=; path[][j]=;}
for(int i=; i<=len1; ++i)
{dp[i][]=; path[i][]=;}
//以上代码用 memset 也行
//memset(dp,0,sizeof(dp));
//memset(path,0,sizeof(path)); for(int i=; i<=len1; ++i)
{
for(int j=; j<=len2; ++j)
{
if(s1[i-]==s2[j-])
{
dp[i][j]=dp[i-][j-]+;
path[i][j]=LEFTUP;
}
else if(dp[i-][j]>dp[i][j-]) //up>=left 这里是用 > 还是 >= ,当LCS不唯一时,对结果有影响,但长度一样
{
dp[i][j]=dp[i-][j];
path[i][j]=UP;
}
else
{
dp[i][j]=dp[i][j-];
path[i][j]=LEFT;
}
}
} //矩阵填充完成
cout<<"length of LCSeq: "<<dp[len1][len2]<<endl; int i=len1, j=len2;
while(i> && j>)
{
if(path[i][j]==LEFTUP)
{
result+=s1[i-];
i--;
j--;
}
else if(path[i][j]==UP) i--;
else if(path[i][j]==LEFT) j--;
}
reverse(result.begin(), result.end());
return result;
} int main()
{
int t;
freopen("in.txt", "r", stdin);
//freopen("out.txt", "w", stdout);
cin>>t;
cout<<"total tests: "<<t<<endl<<endl;
while(t--)
{
string s1,s2;
cin>>s1>>s2;
cout<<s1<<endl<<s2<<endl; string res=LCSeq(s1,s2);
cout<<"LCSeq: "<<res<<endl<<endl; }
return ;
}

运行:

输入:同上

输出:

说一下以上程序中37行的 >= 和 > 的区别。当 LCSeq 不唯一时,讨论此区别才有意义。对于以下两个字符串

s1=BCXCADFESBABCACA  , s2=ABCACADF

取>=和>符号时的求得的LCSeq分别为:

BCCADF 和 ABCACA

【s1=BCXCADFESBABCACA  , s2=ABCACADF】

【s1=BCXCADFESBABCACA  , s2=ABCACADF】

分析:

当取>=符号时,就是说当dp[i][j]上边的数与左边的数相等时,选择上边的数赋给dp[i][j]。这就造成在后来的回溯过程中,回溯的路径“更快地往上走,更慢的往左走”,当回溯结束时,所求的的子序列由“s2的靠后部分 + s1的靠前部分”构成。(这里的“靠前”、“靠后”为相对而言)。

当取>符号时,就是说当dp[i][j]上边的数与左边的数相等时,选择左边的数赋给dp[i][j]。这就造成在后来的回溯过程中,回溯的路径“更快地往左走,更慢的往上走”,当回溯结束时,所求的的子序列由“s1的靠后部分 + s2的靠前部分”构成。

可以参看上面的图来理解这个过程,也可自己画两个图试一下。

参考:

维基百科

http://en.wikipedia.org/wiki/Longest_common_substring_problem

http://en.wikipedia.org/wiki/Longest_common_subsequence_problem

http://en.wikibooks.org/wiki/Algorithm_implementation/Strings/Longest_common_substring

博客园

http://www.cnblogs.com/xudong-bupt/archive/2013/03/15/2959039.html

推荐:

http://blog.sina.com.cn/s/blog_54ce19050100wdvn.html

http://www.cnblogs.com/huangxincheng/archive/2012/11/11/2764625.html

http://my.oschina.net/leejun2005/blog/117167

http://www.cnblogs.com/zhangchaoyang/articles/2012070.html

DP:LCS(最长公共子串、最长公共子序列)的更多相关文章

  1. 最长公共子串(LCS:Longest Common Substring)

    最长公共子串(LCS:Longest Common Substring)是一个非常经典的面试题目,本人在乐视二面中被面试官问过,惨败在该题目中. 什么是最长公共子串 最长公共子串问题的基本表述为:给定 ...

  2. [DP]最长公共子串

    题目 给定两个字符串str1和str2, 长度分别稳M和N,返回两个字符串的最长公共子串 解法一 这是一道经典的动态规划题,可以用M*N的二维dp数组求解.dp[i][j]代表以str1[i]和str ...

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

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

  4. 华为 oj 公共子串计算

    水题,原来以为用dp数组  结果wrong了两次 我想还是自己小题大做了···呵呵·· 献给初学者作为参考 #include <stdio.h> #include <string.h ...

  5. 经典算法-最长公共子序列(LCS)与最长公共子串(DP)

    public static int lcs(String str1, String str2) { int len1 = str1.length(); int len2 = str2.length() ...

  6. 算法设计 - LCS 最长公共子序列&&最长公共子串 &&LIS 最长递增子序列

    出处 http://segmentfault.com/blog/exploring/ 本章讲解:1. LCS(最长公共子序列)O(n^2)的时间复杂度,O(n^2)的空间复杂度:2. 与之类似但不同的 ...

  7. 动态规划经典——最长公共子序列问题 (LCS)和最长公共子串问题

    一.最长公共子序列问题(LCS问题) 给定两个字符串A和B,长度分别为m和n,要求找出它们最长的公共子序列,并返回其长度.例如: A = "HelloWorld"    B = & ...

  8. UVa 10192 - Vacation &amp; UVa 10066 The Twin Towers ( LCS 最长公共子串)

    链接:UVa 10192 题意:给定两个字符串.求最长公共子串的长度 思路:这个是最长公共子串的直接应用 #include<stdio.h> #include<string.h> ...

  9. poj1159 dp最长公共子串

    //Accepted 204 KB 891 ms //dp最长公共子串 //dp[i][j]=max(dp[i-1][j],dp[i][j-1]) //dp[i][j]=max(dp[i][j],dp ...

随机推荐

  1. FPGA的图像处理技术,你知道多少?

    最近一段时间一直在研究基于FPGA的图像处理,乘着EEPW这个机会和大家交流一下,自己也顺便总结一下.主要是为了大家对用FPGA做图像处理有个感性的认识,如果真要研究的话就得更加深入学习了.本人水平有 ...

  2. “==”,比较的是引用 “equals方法”比较的是具体内容

    package com.java1234.chap03.sec08; public class Demo2 { public static void main(String[] args) { //“ ...

  3. 【Qt】Qt之自定义界面(QMessageBox)【转】

    简述 通过前几节的自定义窗体的学习,我们可以很容易的写出一套属于自己风格的界面框架,通用于各种窗体,比如:QWidget.QDialog.QMainWindow. 大多数窗体的实现都是采用控件堆积来完 ...

  4. JavaScript 垃圾回收机制分析

    同C# .Java一样可以手工调用垃圾回收程序,但是由于其消耗大量资源,而且手工调用的不会比浏览器判断的准确,所以不推荐手工调用垃圾回收.   最近精力主要用在了Web 开发上,读了一下<Jav ...

  5. [转]PHP中fopen,file_get_contents,curl的区别

    1.       fopen /file_get_contents 每次请求都会重新做DNS查询,并不对 DNS信息进行缓存.但是CURL会自动对DNS信息进行缓存.对同一域名下的网页或者图片的请求只 ...

  6. C# 获得手机归属地功能

    今天通过查资料了解到web的页面抓取功能,应用HttpWebRequest和HttpWebResponse功能,从http://www.showji.com网站中抓取归属地信息 应该说这个方法是从别的 ...

  7. 1106. Lowest Price in Supply Chain (25)

    A supply chain is a network of retailers(零售商), distributors(经销商), and suppliers(供应商)-- everyone invo ...

  8. hdu 5738 2016 Multi-University Training Contest 2 Eureka 计数问题(组合数学+STL)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5738 题意:从n(n <= 1000)个点(有重点)中选出m(m > 1)个点(选出的点只 ...

  9. PAT乙级真题1008. 数组元素循环右移问题 (20)

    原题: 1008. 数组元素循环右移问题 (20) 时间限制400 ms内存限制65536 kB 一个数组A中存有N(N>0)个整数,在不允许使用另外数组的前提下,将每个整数循环向右移M(M&g ...

  10. Oracle逻辑体系:数据文件黑盒的内在洞天

    select username,session_num,tablespace from v$sort_usage; Block: 块的组成 Header:包含数据块的概要信息:块地址,块属于哪个段,还 ...