7-6 两个字符串的所有最长公共子序列 (15 分)
输入格式
输入长度≤100的两个字符串。
输出格式
输出两个字符串的所有最长公共子序列,若最长公共子序列多于1个,则将所有子序列按字典序从小到大排序后输出。
输入样例1
ABCBDAB
BDCABA
结尾无空行
输出样例1
BCAB
BCBA
BDAB
结尾无空行
输入样例2
ABACDEF
PGHIK
结尾无空行
输出样例2
NO
基本思路
这题的难点不在于数组dp的求解,这一步很基础,难点主要在于如何求出所有LCS(所有完整路径,并按照字典序输出)。一开始我也是没有头绪的,直到搜一些大佬的博客,找到了一张图片:
如果没有分叉点,其实用普通的递推也能求出一条路径,但是这里有若干个分叉点,所以需要用到递推+dfs求出所有LCS路径。
从m、n、空串res(实时路径,递归边界处为完整路径)开始dfs。
在当前某个dfs中:
当i>0&&j>0循环:如果A[i-1]==B[j-1],说明找到了LCS的一个元素,拼接到实时路径res后面;如果 dp[i-1][j]>dp[i][j-1] ,i–;如果dp[i-1][j]<dp[i][j-1],j–;如果dp[i-1][j]==dp[i][j-1],说明当前是一个分叉点,需要往两个方向继续dfs求实时路径(在其中一个dfs中,可以在当前这个dfs中求出的实时路径的基础上继续求解实时路径,直到遇到了递归边界,实时路径变成了完整路径),考虑完这两个dfs的情况后回溯(回溯后当前这个dfs中求出的部分路径将会失效)。
如果i等于0||j等于0时,这时候已经退出了while循环,说明已经到达了递归边界,求出了一条完整路径,将它压入集合s中,回溯(回溯后当前这个dfs中求出的部分路径将会失效)。
注意:递归边界处求出的完整路径为逆序,压入集合s前需要倒转!
代码
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
#include <set>
using namespace std;
int length1;
string A,B;
vector<vector<int>> dp;//定义动态规划表,dp[i][j]表示A[i-1]和B[i-1]前(包括A[i-1]和B[i-1])的LCS的长度
set<string> s;//存放所有的LCS(逆序)
//重新指定数组dp的大小,构造数组dp,返回序列A和B的LCS
int lcs(int m, int n){
//重新指定数组dp的大小为(m+1)*(n+1)
dp = vector<vector<int>>(m+1,vector<int>(n+1));
//遍历数组dp的所有每一个元素,构造数组dp
for(int i=0; i<=m; ++i){
for(int j=0; j<=n; ++j){
//递推边界:第0行第0列的dp值全部置0
/*因为字符串A、B下标从0开始,但是dp表的第0行或第0列已经作为递推边界了:
我们假定把字符串A、B整体向后移动一位来计算数组dp,也就是说字符串A、B中的i-1、j-1相当于数组dp中的i、j。*/
if (i == 0 || j == 0)
dp[i][j] = 0;
else if(A[i-1] == B[j-1])
dp[i][j] = dp[i-1][j-1] + 1;
else
dp[i][j] = max(dp[i-1][j], dp[i][j-1]);
}
}
//返回序列A和B的LCS的长度
return dp[m][n];
}
//求出所有的LCS,放入集合s中
//每个dfs中可以在当前路径res基础上的求部分路径,并把部分路径拼接当前路径res的后面,在某个dfs中,当i>0和j>0时进行while循环:
//如果遇到A[i-1]==B[j-1]或者dp[i-1][j]>dp[i][j-1]或者dp[i-1][j]<dp[i][j-1]的情况,可以一直在while循环中求部分路径(当A[i-1]==B[j-1]时将A[i-1]拼接到res后面)
//如果遇到dp[i-1][j]==dp[i][j-1]的情况,需要分别对这两个方向dfs,在当前路径res基础上的求解部分路径(考虑完这两个dfs,回溯后,当前dfs中求出的部分路径将失效)
//如果遇到边界,将求出的一条完整路径res压入s后回溯(回溯后,当前dfs中求出的部分路径将失效)
void dfs(int i,int j,string res){
while(i>0&&j>0){
if(A[i-1]==B[j-1]){
res.push_back(A[i-1]);
--i;
--j;
}else if(dp[i-1][j]>dp[i][j-1]){
--i;
}else if(dp[i-1][j]<dp[i][j-1]){
--j;
}else if(dp[i-1][j]==dp[i][j-1]){
//将当前路径res值传递给下一个dfs使用,也就是说下面的dfs中可以在这个res的基础上继续求部分路径
dfs(i-1,j,res);
dfs(i,j-1,res);
//上面两种情况已经考虑完毕,相应的序列也压入到了s中,所以可以回溯
//因为上面dfs中的res是局部变量,上面两个dfs求出的部分路径已经失效
//经过下面这个return之后,这个dfs中求出的部分路径也将失效
return;
}
}
//如果i或j其中有一个已经为0了,说明已经到达的递归边界,求出了一条完整路径,将它压入集合s中,回溯
if(i==0||j==0){
reverse(res.begin(),res.end());
s.insert(res);
return;
}
}
int main(){
cin>>A>>B;
int m=A.length(),n=B.length();
//构造数组dp,函数返回序列A和B的LCS的长度
length1=lcs(m,n);
//如果序列A和序列B的LCS的长度为0,输出"NO",返回
if(length1==0){
cout<<"NO"<<endl;
return 0;
}
//求出所有的LCS,放入集合s中
string str="";
dfs(m,n,str);
//输出
for(auto it=s.begin();it!=s.end();it++){
cout<<*it<<endl;
}
return 0;
}