上一期总结了leetcode 中的关于最长回文子串问题的解决方法,大概分为:暴力枚举、动态规划、马拉车算法(Manacher)、中心扩展等,其中有一种可以转换为最长公共子串解决的方法,顿时想到了许多关于子串和子序列的众多问题,其中包括最长公共子序列和最长公共子串的问题,最长递增子序列的问题等等,现在做一下总结。
概念
(1)字符子串:指的是字符串中连续的n个字符,如abcdefg中,ab,cde,fg等都属于它的字串。
(2)字符子序列:指的是字符串中不一定连续但先后顺序一致的n个字符,即可以去掉字符串中的部分字符,但不可改变其前后顺序。如abcdefg中,acdg,bdf属于它的子序列,而bac,dbfg则不是,因为它们与字符串的字符顺序不一致。
(3) 公共子序列:如果序列C既是序列A的子序列,同时也是序列B的子序列,则称它为序列A和序列B的公共子序列。如对序列 1,3,5,4,2,6,8,7和序列 1,4,8,6,7,5 来说,序列1,8,7是它们的一个公共子序列。
1. 最长公共子序列(LCS)
最长公共子序列,英文缩写为LCS(Longest Common Subsequence)。其定义是,一个序列 S ,如果分别是两个或多个已知序列的子序列,且是所有符合此条件序列中最长的,则 S 称为已知序列的最长公共子序列。
最长公共子序列(LCS)也不一定唯一,对于固定的两个数组,虽然最LCS不一定唯一,但LCS的长度是一定的。查找最长公共子序列与查找最长公共子串的问题不同的地方在于:子序列不需要在原序列中占用连续的位置。最长公共子串(要求连续)和最长公共子序列是不同的。
1.1 问题描述
1.2 思路
动态规划
状态:res[i][j]
注意:res[i][j]中的i代表的是字符串A的左边i个字符形成的字符串和字符串B左边j个字符形成的字符串之间的最长公共子序列长度。(与以下说法不同:以A的第i个字符和以B的第j个字符为结尾形成的最长公共子序列,主要不同在是否一定包含第i或j个元素)。
状态转移方程:
初始化:
边界条件:i<A.length(),j<B.length();
输出:res[length1 - 1][length2 - 1];
可以根据这个方程来进行填表,以"helloworld"和“loop”为例:
C++代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<string>
#include<algorithm>
#define m 1010
using namespace std;
int maxlen[m][m];
int maxlensr[m][m];
int length1, length2;
int LCS(string s1, string s2)
{
memset(maxlen, 0, sizeof(int));
length1 = s1.length();
length2 = s2.length();
for (int i = 0; i<length1; i++){//递推出口
maxlen[i][0] = 0;
}
for (int j = 0; j<length2; j++){
maxlen[0][j] = 0;
}
for (int i = 1; i <= length1; i++){
for (int j = 1; j <= length2; j++){
if (s1[i - 1] == s2[j - 1]){
maxlen[i][j] = maxlen[i - 1][j - 1] + 1;//1
}
else{
maxlen[i][j] = max(maxlen[i][j - 1], maxlen[i - 1][j]);//2
}
}
}
printf("%d\n", maxlen[length1][length2]);
return 0;
}
//打印最长公共子序列
void printlcs(string s1, string s2)
{
int i, j, z = 0;
char c[1001];
memset(c, 0, sizeof(c));
i = length1, j = length2;
while (i != 0 && j != 0)
{
if (s1[i - 1] == s2[j - 1])
{
c[z++] = s1[--i];
j--;
}
else if (maxlen[i - 1][j] < maxlen[i][j - 1])
j--;
else if (maxlen[i][j - 1] <= maxlen[i - 1][j])
i--;
}
for (i = z - 1; i >= 0; i--)
printf("%c", c[i]);
printf("\n");
}
int main(){
string s1;
string s2;
cout << "请输入s1:" << endl;
cin >> s1;
cout << "请输入s2:" << endl;
cin >> s2;
LCS(s1, s2);
printlcs(s1, s2);
system("pause");
return 0;
}
2. 最长公共子串
2.1 问题描述
2.2 思路
和最长公共子序列一样,使用动态规划的算法。
但是此时的最长公共子序列和最长公共子串的状态含义并不相同。
注意:res[i][j]此时代表的是以A[i]和B[j]为最后一个元素的最长公共子串,这代表公共字串中包含A[i]和B[j],并且如果最长公共子串存在,则必须包含A[i](B[j],因为A[i]=(B[j])。
状态:res[i][j],代表的是以A[i]和B[j]为最后一个元素的最长公共子串;
状态转移方程:
初始化:当i=0或者j=0,res[i][i]=0,即res[i][0]=res[0][j] =0;
边界条件:i<A.length(),j<B.length();
输出:res[length][length]
最长公共子串和LCS问题唯一不同的地方在于当A[i] != B[j]时,res[i][j]就直接等于0了,因为子串必须连续,且res[i][j] 表示的是以A[i],B[j]截尾的公共子串的长度。因此可以根据这个方程来进行填表,以"helloworld"和“loop”为例:
C++代码:
#include<iostream>
#include<string>
#include<algorithm>
#define m 1010
using namespace std;
int maxlensr[m][m];i
nt length1, length2;
//动态规划求最长公共子串
int LCSubStr(string s1, string s2)
{
int length1 = s1.length(), length2 = s2.length();
memset(maxlensr, 0, sizeof(int));
int result = 0; // To store length of the
// longest common substring
for (int i = 1; i <= length1; i++)
{
for (int j = 1; j <= length2; j++)
{
// The first row and first column
// entries have no logical meaning,
// they are used only for simplicity
// of program
if (i == 0 || j == 0)
maxlensr[i][j] = 0;
else if (s1[i - 1] == s2[j - 1])
{
maxlensr[i][j] = maxlensr[i - 1][j - 1] + 1;
result = max(result, maxlensr[i][j]);
}
else maxlensr[i][j] = 0;
}
}
if (result!=0)
{
cout << "最长公共子串长度为:" << result << endl;
}
else
{
cout << "不存在最长公共子串" << endl;
}
return 0;
}
int main(){
string s1;
string s2;
cout << "请输入s1:" << endl;
cin >> s1;
cout << "请输入s2:" << endl;
cin >> s2;
LCSubStr(s1, s2);
system("pause");
return 0;
}
参考:
https://blog.csdn.net/ggdhs/article/details/90713154
https://blog.csdn.net/lxt_Lucia/article/details/81209962?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.channel_param
https://blog.csdn.net/George__Yu/article/details/75896330