7-6 两个字符串的所有最长公共子序列 (15 分)

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;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值