DP:LCS(最长公共子串、最长公共子序列)
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(最长公共子串、最长公共子序列)的更多相关文章
- 最长公共子串(LCS:Longest Common Substring)
最长公共子串(LCS:Longest Common Substring)是一个非常经典的面试题目,本人在乐视二面中被面试官问过,惨败在该题目中. 什么是最长公共子串 最长公共子串问题的基本表述为:给定 ...
- [DP]最长公共子串
题目 给定两个字符串str1和str2, 长度分别稳M和N,返回两个字符串的最长公共子串 解法一 这是一道经典的动态规划题,可以用M*N的二维dp数组求解.dp[i][j]代表以str1[i]和str ...
- 动态规划1——最长递增子序列、最长公共子序列、最长公共子串(python实现)
目录 1. 最长递增序列 2. 最长公共子序列 3. 最长公共子串 1. 最长递增序列 给定一个序列,找出其中最长的,严格递增的子序列的长度(不要求连续). 解法一:动态规划 通过一个辅助数组记录每一 ...
- 华为 oj 公共子串计算
水题,原来以为用dp数组 结果wrong了两次 我想还是自己小题大做了···呵呵·· 献给初学者作为参考 #include <stdio.h> #include <string.h ...
- 经典算法-最长公共子序列(LCS)与最长公共子串(DP)
public static int lcs(String str1, String str2) { int len1 = str1.length(); int len2 = str2.length() ...
- 算法设计 - LCS 最长公共子序列&&最长公共子串 &&LIS 最长递增子序列
出处 http://segmentfault.com/blog/exploring/ 本章讲解:1. LCS(最长公共子序列)O(n^2)的时间复杂度,O(n^2)的空间复杂度:2. 与之类似但不同的 ...
- 动态规划经典——最长公共子序列问题 (LCS)和最长公共子串问题
一.最长公共子序列问题(LCS问题) 给定两个字符串A和B,长度分别为m和n,要求找出它们最长的公共子序列,并返回其长度.例如: A = "HelloWorld" B = & ...
- UVa 10192 - Vacation & UVa 10066 The Twin Towers ( LCS 最长公共子串)
链接:UVa 10192 题意:给定两个字符串.求最长公共子串的长度 思路:这个是最长公共子串的直接应用 #include<stdio.h> #include<string.h> ...
- 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 ...
随机推荐
- ADO.NET笔记——使用Command执行增删改操作,通过判断ExecuteNonQuery()返回值检查是否操作成功
相关知识: ExecuteNonQuery()方法:执行CommandText属性所制定的操作,返回受影响的记录条数.该方法一般用来执行SQL中的UPDATE.INSERT和DELETE等操作 对于U ...
- php 去除数组中重复元素
去除数组中重复元素, 找了下可以一下两个函数 php array_flip()与array_uniqure() $arr = array(…………) ;// 假设有数组包含一万个元素,里面有重复的元素 ...
- gulp插件
gulp是趋势 gulp完全开发指南 => 快来换掉你的Grunt吧 gulp的工作流程:文件流--文件流--文件流......因为grunt操作会创建临时文件,会有频繁的IO操作,而gulp使 ...
- Ubuntu14.04忘记root密码的解决方法
电脑20多天没用忘记密码了,下面是在网上找到的一个解决办法,其它的和这个也大概相同.因为其中有些缺漏,没能给我解决问题.通过分析最终问题还是解决了,现解决方案的关键点记录一下.希望能方便到其它人. 1 ...
- jquery弹出关闭遮罩层实例
jquery弹出关闭遮罩层实例. 代码如下: <!doctype html public "-//w3c//dtd xhtml 1.0 transitional//en" & ...
- Z-BlogPHP 安装出现 (8) Undefined offset: 6 解决方法
有些cp面板的空间会在每个网页头部和页脚增加两个调用的文件,导致zblogPHP安装出错:(8) Undefined offset: 6 主要国外的主机中PHP配置文件两个选项auto_prepend ...
- js各类共用方法
function GetParameterValueByName(parametername) { var reg = new RegExp("(^|&)" + param ...
- linux下查看进程内存使用情况
1. top命令--动态查看一个进程的内存使用top -d 1 -p pid [,pid ...] //设置为delay 1s,默认是delay 3s 如果想根据内存使用量进行排序,可以shift ...
- About Curah
相信下列场景对您来说一点都不陌生:您遇到一个问题,花了好几个小时在网上搜寻解答和可靠的技术内容.即使前往许多技术博客和论坛翻箱倒柜后,还是无法确定要相信谁,也不知道该选哪个答案. Curah! 网站就 ...
- N个顶点构成多边形的面积
Input 输入数据包含多个测试实例,每个测试实例占一行,每行的开始是一个整数n(3<=n<=100),它表示多边形的边数(当然也是顶点数),然后是按照逆时针顺序给出的n个顶点的坐标(x1 ...