转载自:https://www.cnblogs.com/huashanqingzhu/p/7423745.html

题目链接:http://poj.org/problem?id=1458

题目大意:给出两个字符串,求出这样的一个最长的公共子序列的长度:子序列中的每个字符都能在两个原串中找到,而且每个字符的先后顺序和原串中的先后顺序一致。

输入有若干行,每行是两个字符串。对每一行输入的两个字符串,输出最长公共子串的长度。

Sample Input
abcfbc abfcab
programming contest
abcd mnp

Sample Output
4
2
0

算法分析

参考1:北大郭炜老师mooc课程
参考2:http://blog.csdn.net/u013480600/article/details/40741333

参考3:http://blog.csdn.net/lz161530245/article/details/76943991

输入两个串s1,s2,
设MaxLen(i,j)表示:s1的左边i个字符形成的子串,与s2左边的j个字符形成的子串的最长公共子序列的长度(i,j从0开始算)
MaxLen(i,j) 就是本题的“状态”
假定 len1 = strlen(s1),len2 = strlen(s2)
那么题目就是要求 MaxLen(len1,len2)

显然:
MaxLen(n,0) = 0 ( n= 0…len1)
MaxLen(0,n) = 0 ( n=0…len2)
递推公式:
if(s1[i-1] == s2[j-1]) //s1的最左边字符是s1[0]
    MaxLen(i,j) = MaxLen(i-1,j-1) + 1;
else
    MaxLen(i,j) = Max(MaxLen(i,j-1),MaxLen(i-1,j) );
时间复杂度O(mn),其中m,n是两个字串长度。

关于证明,可以阅读参考2参考3的证明过程。大概过程记录如下:

 我们用Ax表示序列A的连续前x项构成的子序列,即Ax= a1,a2,……ax, By= b1,b2,……by, 我们用LCS(x, y)表示它们的最长公共子序列长度,那原问题等价于求LCS(m,n)。为了方便我们用L(x, y)表示Ax和By的一个最长公共子序列。
让我们来看看如何求LCS(x, y)。我们令x表示子序列,考虑最后一项 第()种情况:Ax = By
那么它们L(Ax, By)的最后一项一定是这个元素!
为什么呢?为了方便,我们令t=Ax=By, 我们用反证法:假设L(x,y)最后一项不是t,
则要么L(x,y)为空序列(别忘了这个),要么L(x,y)的最后一项是Aa=Bb ≠ t, 且显然有a<x,b<y。无论是哪种情况我们都可以把t接到这个L(x,y)后面,从而得到一个更长的公共子序列。矛盾!
如果我们从序列Ax中删掉最后一项ax得到Ax-,从序列By中也删掉最后一项by得到By-,(多说一句角标为0时,认为子序列是空序列),则我们从L(x,y)也删掉最后一项t得到的序列是L(x – , y - )。为什么呢?和上面的道理相同,如果得到的序列不是L(x - , y - ),则它一定比L(x - , y - )短,那么它后面接上元素t得到的子序列L(x,y)也比L(x - , y - )接上元素t得到的子序列短,这与L(x, y)是最长公共子序列矛盾。
因此L(x,y)=L(x-,y-)最后接上元素t,也就是说:
LCS(Ax, By) = LCS(x - , y - ) + 第()种情况:Ax ≠ By
仍然设t=L(Ax,By)的最后一个字符,或者L(Ax,By)是空序列(这时t是未定义值不等于任何值)。
则t≠Ax和t≠By至少有一个成立,因为t不能同时等于两个不同的值嘛!
(2.1) 如果t≠Ax,则有L(x,y)=L(x-,y),因为根本没Ax的事嘛。
也就是说:LCS(x,y) = LCS(x – , y)
(2.2) 如果t≠By,同理有L(x,y)= L(x,y-)。
也就是说:LCS(x,y) = LCS(x, y – )
可是,我们事先并不知道t,由定义,我们取最大的一个,因此这种情况下,有LCS(x,y)=max(LCS(x–,y),LCS(x,y–))。 看看目前我们已经得到了什么结论:
LCS(x,y) =
() LCS(x - ,y - ) + 如果Ax = By
() max(LCS(x – , y) , LCS(x, y – )) 如果Ax ≠ By
这是一个显然的递推式,光有递推可不行,初值是什么呢?
显然,一个空序列和任何序列的最长公共子序列都是空序列!所以我们有:
LCS(x,y) =
() LCS(x - ,y - ) + 如果Ax = By
() max(LCS(x – , y) , LCS(x, y – )) 如果Ax ≠ By
() 如果x=0或者y= 到此我们求出了计算最长公共子序列长度的递推公式。我们实际上计算了一个(n + )行(m + )列的表格(行是0..n,列是0..m),也就这个二维度数组LCS(n,m)。 证明过程
 #include <iostream>
#include <cstring>
using namespace std;
char sz1[];
char sz2[];
int maxLen[][];
int main()
{
while( cin >> sz1 >> sz2 )
{
int length1 = strlen( sz1);
int length2 = strlen( sz2);
int nTmp;
int i,j;
for( i = ;i <= length1; i ++ ) maxLen[i][] = ;
for( j = ;j <= length2; j ++ ) maxLen[][j] = ;
for( i = ;i <= length1;i ++ )
{
for( j = ; j <= length2; j ++ )
{
if( sz1[i-] == sz2[j-] )
maxLen[i][j] = maxLen[i-][j-] + ;
else
maxLen[i][j] = max(maxLen[i][j-],maxLen[i-][j]);
}
}
cout << maxLen[length1][length2] << endl;
}
return ;
}

上面的题目并没有要求输出最长的公共子序列。假如要输出最长公共子序列,可以阅读参考3的代码:(也可以暂时跳过,本文末尾有代码实现。)

 #include <stdio.h>
#include <string.h>
#include <stdlib.h>
int LCSLength(char* str1, char* str2, int **b)
{
int i,j,length1,length2,len;
length1 = strlen(str1);
length2 = strlen(str2); //双指针的方法申请动态二维数组
int **c = new int*[length1+]; //共有length1+1行
for(i = ; i < length1+; i++)
c[i] = new int[length2+];//共有length2+1列 for(i = ; i < length1+; i++)
c[i][]=; //第0列都初始化为0
for(j = ; j < length2+; j++)
c[][j]=; //第0行都初始化为0 for(i = ; i < length1+; i++)
{
for(j = ; j < length2+; j++)
{
if(str1[i-]==str2[j-])//由于c[][]的0行0列没有使用,c[][]的第i行元素对应str1的第i-1个元素
{
c[i][j]=c[i-][j-]+;
b[i][j]=; //输出公共子串时的搜索方向
}
else if(c[i-][j]>c[i][j-])
{
c[i][j]=c[i-][j];
b[i][j]=;
}
else
{
c[i][j]=c[i][j-];
b[i][j]=-;
}
}
}
/*
for(i= 0; i < length1+1; i++)
{
for(j = 0; j < length2+1; j++)
printf("%d ",c[i][j]);
printf("\n");
}
*/
len=c[length1][length2];
for(i = ; i < length1+; i++) //释放动态申请的二维数组
delete[] c[i];
delete[] c;
return len;
}
void PrintLCS(int **b, char *str1, int i, int j)
{
if(i== || j==)
return ;
if(b[i][j]==)
{
PrintLCS(b, str1, i-, j-);//从后面开始递归,所以要先递归到子串的前面,然后从前往后开始输出子串
printf("%c",str1[i-]);//c[][]的第i行元素对应str1的第i-1个元素
}
else if(b[i][j]==)
PrintLCS(b, str1, i-, j);
else
PrintLCS(b, str1, i, j-);
} int main(void)
{
char str1[],str2[];
int i,length1,length2,len;
printf("请输入第一个字符串:");
gets(str1);
printf("请输入第二个字符串:");
gets(str2);
length1 = strlen(str1);
length2 = strlen(str2);
//双指针的方法申请动态二维数组
int **b = new int*[length1+];
for(i= ; i < length1+; i++)
b[i] = new int[length2+];
len=LCSLength(str1,str2,b);
printf("最长公共子序列的长度为:%d\n",len);
printf("最长公共子序列为:");
PrintLCS(b,str1,length1,length2);
printf("\n");
for(i = ; i < length1+; i++)//释放动态申请的二维数组
delete[] b[i];
delete[] b;
system("pause");
return ;
} 求最长公共子序列长度并输出最长公共子序列

查找并输出最长公共子序列也可以参考https://wenku.baidu.com/view/7e96c94f2b160b4e767fcfc9.html

空间上的优化:

观察上面算法中的关键代码:

 for( i = ;i <= length1;i ++ )
{
for( j = ; j <= length2; j ++ )
{
if( sz1[i-] == sz2[j-] ) maxLen[i][j] = maxLen[i-][j-] + ;
else maxLen[i][j] = max(maxLen[i][j-],maxLen[i-][j]);
}
}

可以发现,计算maxLen数组第i行时用到的只有第i行与第i-1行。我们的目的是要计算maxLen[length1][length2],所以,可以考虑只保存两行即可,也就是使用滚动数组只保存两行。

代码如下:(参考来源

cur表示当前需要求的那一行的下标。

 #include <iostream>
#include <cstring>
using namespace std;
char sz1[];
char sz2[];
int maxLen[][];
int main()
{
int i,j,length1,length2,cur=; while( cin >> sz1 >> sz2 )
{
length1 = strlen( sz1);
length2 = strlen( sz2);
for( i=;i<; i++ ) maxLen[i][]=;
for( j=;j<=length2;j++ ) maxLen[][j]=;
cur=; for( i = ;i <= length1;i ++ )
{
cur ^= ;
for( j = ; j <= length2; j ++ )
{
if( sz1[i-] == sz2[j-] )
maxLen[cur][j] = maxLen[cur^][j-] + ;
else
maxLen[cur][j] = max(maxLen[cur][j-],maxLen[cur^][j]);
}
}
cout << maxLen[cur][length2] << endl;
}
return ;
}

下面修改一下代码寻找出一个最长公共子序列。

上面经过空间优化后,也只是寻找到了最长公共子序列的长度,那么如何得到一个最长公共子序列而仅仅不是简单的长度呢?其实我们离真正的答案只有一步之遥。

参考上图,我们建立一个二维数组ans[][],在寻找最长公共子序列的长度时用ans[i][j]记录LCS(i,j)是如何来的(从左边、上边或是从左上),ans[i][j]等于1,2,3分别表示:

L(x,y) = L(x, y – 1)

L(x,y)= L(x – 1, y)

L(x,y) = L(x,- 1 y- 1)末尾接上Ax

当ans[i][j]等于3时字符串1的第i个字符(或字符串2的第j个字符,其实两者相同)肯定是最长公共子序列的一部分,要保留到temp[ ]中。所以从ans[][]右下角逆推即可求出temp[ ],然后逆序输出temp[]即可。代码如下:

 //51Nod动态规划教程例题 求最长公共子序列的长度并输出一个最长公共子序列
#include <iostream>
#include <cstring>
using namespace std;
#define maxN 5005
char sz1[maxN];
char sz2[maxN];
int maxLen[][maxN];
char ans[maxN][maxN]={}; void printLCS(int len1,int len2);//输出一个最长公共子序列
int main()
{
int i,j,length1,length2,cur=;
freopen("poj1458.in","r",stdin);
while( cin >> sz1 >> sz2 )
{
memset(ans,,sizeof(char)*maxN*maxN);
length1 = strlen( sz1);
length2 = strlen( sz2);
for( i=;i<; i++ ) maxLen[i][]=;
for( j=;j<=length2;j++ ) maxLen[][j]=;
cur=; for( i = ;i <= length1;i ++ )
{
cur ^= ;
for( j = ; j <= length2; j ++ )
{
if( sz1[i-] == sz2[j-] )
{
maxLen[cur][j] = maxLen[cur^][j-] + ;
ans[i][j]=;
}
else
{
//maxLen[cur][j] = max(maxLen[cur][j-1],maxLen[cur^1][j]);
if(maxLen[cur][j-]>maxLen[cur^][j])
{
maxLen[cur][j]=maxLen[cur][j-];
ans[i][j]=;
}
else
{
maxLen[cur][j]=maxLen[cur^][j];
ans[i][j]=;
}
}
}
}
cout << maxLen[cur][length2] << endl;
if(maxLen[cur][length2]>) printLCS(length1,length2);
}
return ;
}
void printLCS(int len1,int len2)//输出一个最长公共子序列
{
char temp[maxN];
int i=len1,j=len2,k=;
while(ans[i][j]!=)
{
if(ans[i][j]==) { temp[k++]=sz1[i-]; i--;j--; }
else if(ans[i][j]==)
{
j--;
}
else if(ans[i][j]==)
{
i--;
}
}
for(k--;k>=;k--) printf("%c",temp[k]);
printf("\n");
}

最长公共子序列LCS(POJ1458)的更多相关文章

  1. POJ 1458 Common Subsequence(最长公共子序列LCS)

    POJ1458 Common Subsequence(最长公共子序列LCS) http://poj.org/problem?id=1458 题意: 给你两个字符串, 要你求出两个字符串的最长公共子序列 ...

  2. 1006 最长公共子序列Lcs

    1006 最长公共子序列Lcs 基准时间限制:1 秒 空间限制:131072 KB 给出两个字符串A B,求A与B的最长公共子序列(子序列不要求是连续的). 比如两个串为: abcicba abdks ...

  3. 动态规划之最长公共子序列LCS(Longest Common Subsequence)

    一.问题描述 由于最长公共子序列LCS是一个比较经典的问题,主要是采用动态规划(DP)算法去实现,理论方面的讲述也非常详尽,本文重点是程序的实现部分,所以理论方面的解释主要看这篇博客:http://b ...

  4. 编程算法 - 最长公共子序列(LCS) 代码(C)

    最长公共子序列(LCS) 代码(C) 本文地址: http://blog.csdn.net/caroline_wendy 题目: 给定两个字符串s,t, 求出这两个字符串最长的公共子序列的长度. 字符 ...

  5. C++版 - Lintcode 77-Longest Common Subsequence最长公共子序列(LCS) - 题解

    版权声明:本文为博主Bravo Yeung(知乎UserName同名)的原创文章,欲转载请先私信获博主允许,转载时请附上网址 http://blog.csdn.net/lzuacm. C++版 - L ...

  6. 51Nod 1006:最长公共子序列Lcs(打印LCS)

    1006 最长公共子序列Lcs  基准时间限制:1 秒 空间限制:131072 KB 分值: 0 难度:基础题  收藏  关注 给出两个字符串A B,求A与B的最长公共子序列(子序列不要求是连续的). ...

  7. 51nod 1006 最长公共子序列Lcs 【LCS/打印path】

    1006 最长公共子序列Lcs  基准时间限制:1 秒 空间限制:131072 KB 分值: 0 难度:基础题  收藏  关注 给出两个字符串A B,求A与B的最长公共子序列(子序列不要求是连续的). ...

  8. 每日一题-——最长公共子序列(LCS)与最长公共子串

    最长公共子序列(LCS) 思路: 代码: def LCS(string1,string2): len1 = len(string1) len2 = len(string2) res = [[0 for ...

  9. 51nod 1006:最长公共子序列Lcs

    1006 最长公共子序列Lcs 基准时间限制:1 秒 空间限制:131072 KB 分值: 0 难度:基础题  收藏  关注 给出两个字符串A B,求A与B的最长公共子序列(子序列不要求是连续的). ...

随机推荐

  1. Momenta电话面试笔记

  2. browserify运行原理分析

    目前对于前端工程师而言,如果只针对浏览器编写代码,那么很简单,只需要在页面的script脚本中引入所用js就可以了. 但是某些情况下,我们可能需要在服务端也跑一套类似的逻辑代码,考虑如下这些情景(以n ...

  3. [译]通往 Java 函数式编程的捷径

    原文地址:An easier path to functional programming in Java 原文作者:Venkat Subramaniam 译文出自:掘金翻译计划 以声明式的思想在你的 ...

  4. Centos6下关于系统用户密码规则-运维笔记

    随着linux使用的普遍,对于linux用户以及系统的安全要求越来越高,而用户密码复杂程度是系统安全性高低的首要体现.因此如何对linux下用户的密码进行规则限制,以保证用户必须使用复杂的密码,杜绝用 ...

  5. #个人博客作业Week1——流行的源程序版本管理软件和项目管理软件

    1.TFS(Team Foundation Server)(1)定义:TFS是一个高可扩展.高可用.高性能.面向互联网服务的分布式文件系统,主要针对海量的非结构化数据,          它构筑在普通 ...

  6. Week2 关于代码规范的一些认识

    代码规范 我觉得代码规范是有必要的,而对于以下的四个观点我要提出自己的反驳: 这些规范都是官僚制度下产生的浪费大家的编程时间.影响人们开发效率, 浪费时间的东西 首先应该明白,什么是“编码规范”?它不 ...

  7. 《Linux内核设计与实现》第4章读书整理

    第四章   进程调度 4.1多任务 无论在单处理器或者多处理机器上,多任务操作系统都能使多个进程处于堵塞或者睡眠状态. 非抢占式多任务:除非进程自己主动停止运行,否则它会一直执行. 抢占式多任务:进程 ...

  8. Opentsdb分布式安装

    Opentsdb分布式安装 --李琦 1.下载文件上传到虚拟机 -rw-r--r--.  1 root   root  76793860 Apr 27 10:56 opentsdb-2.2.0.tar ...

  9. David Silver强化学习Lecture1:强化学习简介

    课件:Lecture 1: Introduction to Reinforcement Learning 视频:David Silver深度强化学习第1课 - 简介 (中文字幕) 强化学习的特征 作为 ...

  10. PAT 1003 我要通过!

    https://pintia.cn/problem-sets/994805260223102976/problems/994805323154440192 “答案正确”是自动判题系统给出的最令人欢喜的 ...