目录
题目原文
11077 最长公共子字符串(优先做)
时间限制:1000MS 代码长度限制:10KB
提交次数:0 通过次数:0
题型: 编程题 语言: G++;GCC;VC;JAVA
Description
求两个输入序列的最长的公共子字符串的长度。子字符串中的所有字符在源字符串中必须相邻。 如字符串:21232523311324和字符串312123223445,他们的最长公共子字符串为21232,长度为5。
输入格式
两行,第一行为第一个字符串X,第二行为第二个字符串Y,字符串不含空格并以回车标示结束。X和Y的串长都 不超过10000。
输出格式
两行,第一行为最长的公共子字符串的长度,第二行输出一个最长的公共子字符串。 说明: (1)若最长的公共子字符串有多个,请输出在源字符串X中靠左的那个。 (2)若最长公共子字符串的长度为0,请输出空串(一个换行符)。 如输入: 21232523311324 152341231 由于523和123都是最长的公共子字符串,但123在源串X中更靠左,因此输出: 3 123
输入样例
21232523311324 312123223445
输出样例
5 21232
提示
一,对输入字符串的处理
大家在接受数据的时候,不要用(c=getchar())!='\n'诸如此类一个字符一个字符接受,然后判断是否是回车
符号来结束一行的输入,这样的方式在你本机运行不会有问题,但OJ系统中会有错误,无法输出结果,因为
测试平台行末并非'\n'字符。这里接受数据用scanf的%s,或cin等,会自动判别结束字符的,你就不要在你
程序里专门去判别或吸收回车字符。
二,递推公式
此题和书上3.3节"最长公共子序列"描述是不同的,子序列可由不连续字符组成,但子字符串是连续的。
此题更加简单!
假设求字符串str1,str2的最长公共子串的长度.
定义f(m,n): 分别以str1[m],str2[n]结尾的最长连续公共子串的长度,
其中字符串末尾的str1[m]和str2[n]包含在最长公共子串中,即为最长公共子串的最末元素。
(这里大家思考一下,为何要这样假设子问题和子问题最优解f(m,n)?
因为子串是连续的,更大规模问题和下一级更小规模的子问题要能联系起来,而且这种联系还要越简单越好,
只有规定原先两个串的最末元素包含在最长公共子串中,这样就能联系上两个串的前缀部分(都去掉末个元
素)的最长公共子串问题。)
而对于f(m+1,n+1) 有:
1) f(m+1,n+1) = 0, if str1[m+1] != str2[n+1]
2) f(m+1,n+1) = f(m,n) + 1, if str1[m+1] == str2[n+1]
3) 另外边界情况,f(0,j)=0(j>=0), f(j,0)=0(j>=0)
而此题所求的最长公共字符串的长度即为f(m,n)整个二维数组中的最大值,注意不是填充的最后一个元素。
也不是最后一行元素的最大值,而是整个二位数组的最大值。思考一下为什么呢?
至于如何优先输出在源串X靠左的公共子串,大家自行思考。
这也容易,在你比较产生数组最大值maxlen时,就同步记录下那时在x数组中的下标位置,比如此位置叫endindex_x。
最后用这个位置在X序列中输出从X[endindex_x - maxlen + 1]元素一直到X[endindex_x]元素即可。
文字思路
这是一道动态规划问题。动态规划和纯递归以及暴力相比的好处就是大量削减时间复杂度,把问题从指数级的时间复杂度变为o(n²)或者o(n)。
根据题目要求,需找到最大公共子字符串。暴力求解法就是遍历出给定两个序列所有的子字符串,再找出完全一样且长度最长且靠近序列左边的最大公共字符串,需要o(max(m,n)!)的时间复杂度,如果把每个序列都记录下来,空间复杂度也是o(max(m,n)!)。采用动态规划法后时间复杂度变为了o(n²),空间复杂度变为了o(m*n),明显缩减问题规模。
求最大公共字符串的代码逻辑:
对第一个序列进行标号的遍历,对于第一个序列的每个标号i对应的元素,如果在第二个序列中j的遍历找到对应的值,则记录下当前的i,j,使从第一个序列i对应字符开始的最大公共字符串长度加1(这两步用一个数组len_com_s实现)。当i跳变为i+1后,继续对j进行遍历,此时①如果在j中找到和i+1对应的数,len_com_s[i][j]=len_com_s[i-1][j-1]+1。如果在i未跳变前已经找到对应元素,则len_com_s[i-1][j-1]不为0,此时len_com_s[i][j]就是当前以第一个序列的i标号为第一个元素的公共字符串的长度。②如果没找到,len_com_s[i][j]=0。
递归写法:
定义f(m,n): 分别以str1[m],str2[n]结尾的最长连续公共子串的长度,
其中字符串末尾的str1[m]和str2[n]包含在最长公共子串中,即为最长公共子串的最末元素。
而对于f(m+1,n+1) 有:
1) f(m+1,n+1) = 0, if str1[m+1] != str2[n+1]
2) f(m+1,n+1) = f(m,n) + 1, if str1[m+1] == str2[n+1]
3) 另外边界情况,f(0,j)=0(j>=0), f(j,0)=0(j>=0)
求最长公共子序列
如果不求最大字符串,求最长公共子序列(可以是非连续的),则条件改为
1) f(m+1,n+1) = f(m,n) + 1, if str1[m+1] == str2[n+1]
2)f(m+1,n+1) = f(m,n+1), if str1[m+1] != str2[n+1]&&f(m,n+1)>=f(m,n+1)
3) f(m+1,n+1) = f(m,n+1), if str1[m+1] != str2[n+1]&&f(m,n+1)<f(m,n+1)
4) 另外边界情况,f(0,j)=0(j>=0), f(j,0)=0(j>=0)
下证明前三个条件:
设标号从0到m+1的str1序列为Xm+1,设标号从0到n+1的str2序列为Yn+1,他们的最长公共子序列为Zk+1,他们中的各元素为xi,yj,zk。
对于1),即证当xm+1=yn+1=zk+1时,Xm+1和Yn+1有最长公共子序列Zk+1。
反证法:若yn+1≠zk+1,那么Zk+1也是Yn的长度为k+1最长公共子序列。此时在Yn后加入一个xn+1,则Yn+1有长度为k+2的最长公共子序列,与已知矛盾。
对于2)和3),即证当xm+1≠yn+1时,Xm+1和Yn+1的最长公共子序列为Xm和Yn+1的最长公共子序列和Xm+1和Yn的最长公共子序列中取较长者。
假设Z是Xm+1与Yn+1的最长公共子序列,若zk+1=xm+1,由1)知Xm与Yn+1可能成为这个公共子序列。若zk+1=yn+1,同理Xm+1与Yn可能成为这个公共子序列。它们之间的较长者(考虑到最后一个元素的作用)便是Xm+1与Yn+1的最长公共子序列。
tips
虽然题目说明两个字符串长都不超过10000,但给他们分配空间500就够了,不然会超时。
11077代码
#include <iostream>
#include <stdio.h>
#include <string.h>
using namespace std;
void find_max_common_string(char s1[],char s2[],int len_com_s[500][500],int len1,int len2)
{
int i=0,j=0;
//遍历求两个输入序列的所有子字符串的最大公共字符串长度并存在len_com_s[][]中
int max_len=0;
int max_len_i=0;//记录第一次出现最大公共字符串长度时该字符串末尾元素在第一个输入序列的索引
for(i=0;i<=len1-1;i++)
{
for(j=0;j<=len2-1;j++)
{
if(s1[i]==s2[j])//记录字符串长
{
if((i>0)&&(j>0))
len_com_s[i][j]=len_com_s[i-1][j-1]+1;
else
len_com_s[i][j]=1;
if(len_com_s[i][j]>max_len)
{
max_len_i=i;
max_len=len_com_s[i][j];
}
}
else if(s1[i]!=s2[j])//不再记录字符串的状态
len_com_s[i][j]=0;
}
}
cout<<max_len<<endl;
for(int i=max_len_i-max_len+1;i<=max_len_i;i++)
cout<<s1[i];
if(!max_len)//最大公共字符串为空,则输出空格
cout<<' ';
}
int main()
{
char string_1[500]={0};
char string_2[500]={0};
//读取字符串
scanf("%s",&string_1);
scanf("%s",&string_2);
int len_string_1=strlen(string_1);
int len_string_2=strlen(string_2);
int len_common_string[500][500]={{0}};
find_max_common_string(string_1,string_2,len_common_string,len_string_1,len_string_2);
return 0;
}
浙公网安备 33010602011771号