第一次写这种,为了学习,有错直言,见谅
我们称Z=<z1,z2,z3,z4....zn>是序列X=<x1,x2,x3,x4..xm>的子序列当且仅当存在严格上升的序列<i1,i2,i3,i4...in>使得j=1,2,3,...k,有Xij=Zj。
例子说明子序列: 如果A=<a,b,w,d>,B=<a,b,d>,那么B就是A的一个子序列,当然还有其他的子序列.
最大公共子学列:如果给出两个序列X,Y,就是要找到一个最长的子序列Z,使其既是X的子序列,也是Y的子序列。
应该就需要这两个概念吧。。
然后是递推关系
if(i == 0 || j == 0) {
maxlen(i,j)=0;
}
else if(s1[i] == s2[i]) {
maxlen(i,j) = maxlen(i-1,j-1)+1;
}
else {
maxlen(i,j) = max(maxlen(i,j-1),maxlen(i-1,j));
}
说明s1,s2是存放两个字符串的数组,字符标号从1开始方便理解,比如asd中的第2个字符是s。maxlen(i,j)是s1i和s2j构成的最长公共子序列的长度
maxlen(i,j)=0,表示两个空串的最长公共子序列的长度是0.
其中else {
maxlen(i,j) = max(maxlen(i,j-1),maxlen(i-1,j));
}的递推关系maxlen(i,j) = max(maxlen(i,j-1),maxlen(i-1,j));中一定有一个是比maxlen(i,j)大的
需要用反证法证明 在s1和s2中maxlen(i,j) 不可能比maxlen(i,j-1)和maxlen(i-1,j)都大。
比如假设一:maxlen(i,j)比maxlen(i,j-1)大,i不变,那一定是s1[i]的作用,也就是说s1[i]肯定是最长公共子序列的最后一个字符。
二:maxlen(i,j)比maxlen(i-1,j)大,j不变,那一定是s2[j]的作用。也就是说s2[j]肯定是最长公共子序列的最后一个字符。
那么s1[i]==s2[j]。与else不符。所以递推关系成立 。也可以找简单的两个字符串试试
再就是模板
for(int i=0; i<=len1; i++)//初始化dp数组
dp[i][0]=0;
for(int i=0; i<=len2; i++)
dp[0][i]=0;
for(int i=1; i<=len1; i++) {//核心
for(int j=1; j<=len2; j++) {
if(str1[i] == str2[j])
dp[i][j] = dp[i-1][j-1]+1;
else {
dp[i][j]= max(dp[i-1][j],dp[i][j-1]);
例题:求最大公共子序列
输入包括多组数据,每组数据给出两个不超过200的字符串表示两个序列,两个字符串之间由若干个字符串分开。
输入样例:
abcfbc abfcab
asd kjl
count aont
输出样例
4
0
3
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<iostream>
using namespace std;
#define max1 1000
char str1[max1];//定义两个字符串
char str2[max1];
int dp[max1][max1];//这里的dp二维组就是最长公共子序列相当于maxlen[max1][max1];
int main()
{
while(scanf("%s%s",str1+1,str2+1)>0)//+1字符串地址从1开始,而不是0
{
int len1 = strlen(str1+1);
int len2 = strlen(str2+1);
int tmp;
for(int i=0; i<=len1; i++)//初始化dp数组,字符串均不为空,地址从1开始
dp[i][0]=0;
for(int i=0; i<=len2; i++)
dp[0][i]=0;
for(int i=1; i<=len1; i++)//动态规划核心
{
for(int j=1; j<=len2; j++)
{
if(str1[i] == str2[j])
dp[i][j] = dp[i-1][j-1]+1;
else{
dp[i][j]= max(dp[i-1][j],dp[i][j-1]);
//注释里的更容易理解
/*int len1 = dp[i][j-1];
int len2 = dp[i-1][j];
if(len1 > len2)
dp[i][j] = len1;
else
dp[i][j] = len2;*/
}
}
}
printf("%d\n",dp[len1][len2]);//输出最长公共子序列
}
return 0;
}
附一道例题
问题 D: 回文字符串
题目描述
输入
接下来的N行,每行一个字符串,每个字符串长度不超过1000.
输出
样例输入
1
Ab3bd
样例输出
2
本题只给出一个字符串,用到头文件#include<string.h>里的reverse逆置字符串。
要求每行输出所需添加的最少字符串数,转化成求原字符串与其逆置字符串的最长公共子序列的长度,
然后用原字符串的长度减去最长公共子序列的长度就是应该添加的字符串数
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
int dp[1005][1005];
char s1[1005];
char s2[1005];
int main()
{
int i,j,t;
scanf("%d",&t);
while(t--)
{
scanf("%s",s1);
int len=strlen(s1);
strcpy(s2,s1);//将字符串s2复制到s1
reverse(s2,s2+len);//求出原字符串的逆置字符串
//求两个字符串的最大公共子序列的长度 dp[len][len]
for(i=0;i<=len;i++)//初始化dp数组
{
dp[i][0]=0;
dp[0][i]=0;
}
for(i=1;i<=len;i++)//动规核心
{
for(j=1;j<=len;j++)
{
/*dp[i][j]=s1[i-1]==s2[j-1]?dp[i-1][j-1]+1:max(dp[i-1][j],dp[i][j-1]); //三目式*/
if(s1[i-1]==s2[j-1])
dp[i][j]=dp[i-1][j-1]+1;
else
dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
}
}
printf("%d\n",len-dp[len][len]);
}
return 0;
}
参考资料《程序设计导引》以及学长的代码 2017.8.20