最长公共子序列
给定两个字符串,按照字符串顺序(下标递增)找出两个字符串所包含的相同字符所组成的最长子序列,子序列的元素在原来字符串中的下标不一定连续,但一定递增。
最长公共子串
给定两个字符串,按照字符串顺序(下标递增)找出两个字符串所包含的长度最长的公共的字符串,即找出一个字符串,两个给定的字符串都包含且长度最大。
举一个例子:
假设现有字符串 s1="cosdenn" s2="csdn":s1与s2的最长公共子序列为 csdn,s1与s2的最长公共子串为sd。
最长公共子序列
查找过程如下:
1、s1的第一个字符 'c' 与s2的第一个字符相同,因此,'c' 作为公共子序列的第一个字符,继续往下查找。
2、s1的第二个字符 'o' 在s2中不存在,因此,继续往下查找。
3、s1的第三个字符 's' 跟s2的第二个字符相同,因此,'s' 作为公共子序列的第二个字符,继续往下查找。
4、s1的第四个字符 'd' 跟s2的第三个字符相同,因此,'d' 作为公共子序列的第三个字符,继续往下查找。
5、s1的第五个字符 'e'在s2中不存在,因此,继续往下查找。
6、s1的第六个字符 'n' 跟s2的第四个字符相同,因此,'n' 作为公共子序列的第四个字符,至此s2已经查找到最后一个字符,查找结束。
最终,s1与s2的最长公共子序列为csdn。
思路:
1、把两个字符串分别作为行和列组成一个二维数组dp[][]。
2、比较二维数组中每个点对应行列字符中否相等,相等的话值设置为1,否则设置为0。
3、连接每行中为1的字符,组成的字符序列即为最长公共子序列。
整理为表格如下:
| c | s | d | n |
c | 1 | 0 | 0 | 0 |
o | 0 | 0 | 0 | 0 |
s | 0 | 1 | 0 | 0 |
d | 0 | 0 | 1 | 0 |
e | 0 | 0 | 0 | 0 |
n | 0 | 0 | 0 | 1 |
n | 0 | 0 | 0 | 1 |
n | 0 | 0 | 0 | 1 |
整理思路,可以发现,我们可在查找过程中对最长子序列长度进行更新。
思路优化如下:
基本思路
因为下标从0开始,实际操作思路为:
更新表格如下:
| c | s | d | n |
c | 1 | 1 | 1 | 1 |
o | 1 | 1 | 1 | 1 |
s | 1 | 2 | 2 | 2 |
d | 1 | 2 | 3 | 3 |
e | 1 | 2 | 3 | 3 |
n | 1 | 2 | 3 | 4 |
n | 1 | 2 | 3 | 4 |
n | 1 | 2 | 3 | 4 |
代码 :
///求解最长公共子序列
string GetLcs1(string s1,string s2){
int len1=s1.length();
int len2=s2.length();
int dp[len1][len2];
int max1=0,id=-1;
string s="";
for(int i=0;i<len1;i++){
for(int j=0;j<len2;j++){
if(s1[i]==s2[j]){
if(i==0||j==0)
dp[i][j]=1;
else
dp[i][j]=dp[i-1][j-1]+1;
if(dp[i][j]>max1){
max1=dp[i][j];
s+=s1[i];
}
}else{
if(i==0&&j==0)///数组第一个元素
dp[i][j]=0;
else if(i==0&&j>0)///第一行
dp[i][j]=dp[i][j-1];
else if(i>0&&j==0)///第一列
dp[i][j]=dp[i-1][j];
else
dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
}
}
}
return s;
}
最长公共子串
查找过程如下:
1、s1的第一个字符 'c' 与s2的第一个字符相同,因此,‘’c" 加入到s1与s2的最长公共子串备选,当前最长公共子串备选为{"c"},继续往下查找。
2、s1的第二个字符 'o' 在s2中不存在,因此,当前最长公共子串备选仍为{"c"}, 继续往下查找。
3、s1的第三个字符 's' 跟s2的第二个字符相同,因此,"s" 被加入s1与s2的最长公共子串备选,当前最长公共子串备选为{"c","s"},继续往下查找。
4、s1的第四个字符 "s" 跟s2的第三个字符相同,因此,"sd" 加入s1与s2的最长公共子串备选,当前最长公共子串备选为{"c","sd"},继续往下查找。
5、s1的第五个字符 'e' 在s2中不存在,因此,当前最长公共子串备选仍为{"c","sd"},继续往下查找。
6、s1的第六个字符 'n' 跟s2的第四个字符相同,因此,'n' 加入s1与s2的最长公共子串备选,当前最长公共子串备选为{"c","sd","n"}。至此,s2已经查找到最后一个字符,查找结束。
最终,从最长公共子串备选中找出最长的字符串为“sd”。
思路:
1、把两个字符串分别作为行和列组成一个二维数组dp[][]。
2、比较二维数组中每个点对应行列字符中否相等,相等的话值设置为1,否则设置为0。
3、表格中值为1的最长对角线处的字符组成的字符串即为最长公共子串。
整理为表格如下:
| c | s | d | n |
c | 1 | 0 | 0 | 0 |
o | 0 | 0 | 0 | 0 |
s | 0 | 1 | 0 | 0 |
d | 0 | 0 | 1 | 0 |
e | 0 | 0 | 0 | 0 |
n | 0 | 0 | 0 | 1 |
n | 0 | 0 | 0 | 1 |
n | 0 | 0 | 0 | 1 |
思路优化如下:
更新表格如下:
| c | s | d | n |
c | 1 | 0 | 0 | 0 |
o | 0 | 0 | 0 | 0 |
s | 0 | 1 | 0 | 0 |
d | 0 | 0 | 2 | 0 |
e | 0 | 0 | 0 | 0 |
n | 0 | 0 | 0 | 1 |
n | 0 | 0 | 0 | 1 |
n | 0 | 0 | 0 | 1 |
代码:
///求解最长公共子串
string GetLcs2(string s1,string s2){
int len1=s1.length();
int len2=s2.length();
int dp[len1][len2];
int max1=0,id=-1;
for(int i=0;i<len1;i++){
for(int j=0;j<len2;j++){
if(s1[i]==s2[j]){
if(i==0||j==0){
dp[i][j]=1;
}else{
dp[i][j]=dp[i-1][j-1]+1;
}
if(dp[i][j]>max1){
max1=dp[i][j];
id=i;
}
}else{
dp[i][j]=0;
}
}
}
return s1.substr(id-max1+1,max1);
}
测试完整程序
#include<bits/stdc++.h>
using namespace std;
///求解最长公共子序列
string GetLcs1(string s1,string s2){
int len1=s1.length();
int len2=s2.length();
int dp[len1][len2];
int max1=0,id=-1;
string s="";
for(int i=0;i<len1;i++){
for(int j=0;j<len2;j++){
if(s1[i]==s2[j]){
if(i==0||j==0)
dp[i][j]=1;
else
dp[i][j]=dp[i-1][j-1]+1;
if(dp[i][j]>max1){
max1=dp[i][j];
s+=s1[i];
}
}else{
if(i==0&&j==0)///数组第一个元素
dp[i][j]=0;
else if(i==0&&j>0)///第一行
dp[i][j]=dp[i][j-1];
else if(i>0&&j==0)///第一列
dp[i][j]=dp[i-1][j];
else
dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
}
}
}
for(int i=0;i<len1;i++){
for(int j=0;j<len2;j++)
cout<<dp[i][j]<<" ";
cout<<endl;
}
return s;
}
///求解最长公共子串
string GetLcs2(string s1,string s2){
int len1=s1.length();
int len2=s2.length();
int dp[len1][len2];
int max1=0,id=-1;
for(int i=0;i<len1;i++){
for(int j=0;j<len2;j++){
if(s1[i]==s2[j]){
if(i==0||j==0)
dp[i][j]=1;
else
dp[i][j]=dp[i-1][j-1]+1;
if(dp[i][j]>max1){
max1=dp[i][j];
id=i;
}
}else
dp[i][j]=0;
}
}
for(int i=0;i<len1;i++){
for(int j=0;j<len2;j++)
cout<<dp[i][j]<<" ";
cout<<endl;
}
return s1.substr(id-max1+1,max1);
}
int main(){
string s1="cosdennn";
string s2="csdn";
///最大公共序列
cout<<GetLcs1(s1,s2)<<endl;
///最长公共子串
cout<<GetLcs2(s1,s2);
return 0;
}