子序列:给定一个序列X=<x1,x2,x3,x4...,xm>,另一个序列Z=<z1,z2,z3,z4...,zk>,若存在一个严格递增的X的下标序列<i1,i2,i3,...,ik>对所有的1,2,3,...,k,都满足x(ik)=zk,则称Z是X的子序列(不一定要连续但顺序必须相同)
补充:字符子串和字符子序列的区别
字符字串指的是字符串中连续的n个字符;如palindrome中,pa,alind,drome等都属于它的字串
而字符子序列指的是字符串中不一定连续但先后顺序一致的n个字符;如palindrome中,plind,lime属于它的子序列,而mod,rope则不是,因为它们与字符串的字符顺序不一致。
那么如何由dp求出LCS呢?
(1)当元素值等于上方相邻元素值时i-1,即dp[i][j]=dp[i-1][j]
(2)当元素值等于左方相邻元素值时j-1,即dp[i][j]=dp[i][j-1]
(3)若元素值与上方、左方元素均不相等时,说明字符相同了,将a[i]添加到数组中
LCS算法由于使用了两重循环,所以对于长度分别为m、n的序列,其时间复杂度和空间复杂度都是O(mn)
图解如下:
完整代码如下:
#include <stdio.h>
#include <string.h>
using namespace std;
const int MAXSIZE=1001;
char a[MAXSIZE];
char b[MAXSIZE];
int c[MAXSIZE][MAXSIZE];
char dp[MAXSIZE][MAXSIZE];
void LCS(int m,int n){
//先初始化
for(int i=0;i<=m;i++) c[i][0]=0;
for(int j=0;j<=n;j++) c[0][j]=0;
//两重循环处理a、b的所有字符
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
if(a[i]==b[j]){
c[i][j]=c[i-1][j-1]+1;
dp[i][j]='W'; //此时有相同的字符
}else if(c[i-1][j]>=c[i][j-1]){
c[i][j]=c[i-1][j];
dp[i][j]='U'; //此时没有相同的字符
}else{
c[i][j]=c[i][j-1];
dp[i][j]='L'; //此时也没有相同的字符
}
}
}
}
void Print(int m,int n){
int i=m;
int j=n;
if(i==0||j==0) return;
if(dp[i][j]=='W'){
Print(i-1,j-1); //相同,沿对角线走,且需要输出
printf("%c",a[i]);
}else if(dp[i][j]=='U'){
Print(i-1,j); //向上走
}else Print(i,j-1); //向左走
}
int main(){
scanf("%s %s",a+1,b+1);
int m=strlen(a+1);
int n=strlen(b+1);
LCS(m,n);
Print(m,n);
}