子序列与子串问题(最长公共、最长递增)

上一期总结了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

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值