PTA-两个字符串的所有最长公共子序列

一、题目

求两个字符串的所有最长公共子序列。

输入格式:
输入长度≤100的两个字符串。

输出格式:
输出两个字符串的所有最长公共子序列,若最长公共子序列多于1个,则将所有子序列按字典序从小到大排序后输出。

在这里插入图片描述

二、代码

/*
    描述:求解最长公共子序列
    日期:221031
*/
#include <iostream>
#include <set>
using namespace std;

int dp[101][101] = {0};
set<string> res;
string s1, s2;

/*
    如何获取最大子串长度?
    1、dp[i][j]用于保存s1的前i个和s2的前j个的最大子串长度。
    2、状态转移方程:
        dp[i][j]=
            (1)if(s1[i]==s2[j]) dp[i][j]=dp[i-1][j-1]+1
                若是两个字母相等那么,说明可以再进一步,也即+1,也就是说,s1的前i-1个和s2的前j-1个
                的最大长度可以再加上s1[i](s2[j])这个字母
            (2)else dp[i][j]=max(dp[i-1][j], dp[i][j-1]);
                此时说明s1[i]!=s2[j],那么不能加上这个字母,那么不能加上又是多少呢?就看
                s1的前i-1个和s2的前j个,s1的前i个和s2的前j-1个,这两个哪个大就选哪个,因为毕竟又多了
                一个字母,肯定要在它们的基础上继承,所以继承一个更大的
*/
int max_len(int m, int n)
{
    for (int i = 1; i <= m; i++)
    {
        for (int j = 1; j <= n; j++)
        {
            if (s1[i-1] == s2[j-1])
            //这里有些绕,之所以用i-1和j-1,是因为在字符串中,是从0开始的,而dp数组中
            //0行和0列没有意义,不代表字符串中的内容
                dp[i][j] = dp[i - 1][j - 1] + 1;
            else
                dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
        }
    }
    return dp[m][n];
}

void rec_save_str(int i,int j,string str)
{
    while(i>0&&j>0)
    {
        if(s1[i-1]==s2[j-1])
        //当两个字母相等的时候,那么双指针i,j同时减减,并且对str做头插入
        //因为整个获取str的过程从后向前的
        {
            j--;
            i--;
            str=s1[i]+str;
        }
        else if(dp[i-1][j]<dp[i][j-1])
        //意思是s1的前i-1个和s2的前j个的最大子串长度小于s1的前i个和s2的前j-1个
        //说明s1[i]这个字母是重要的,一旦没了它,最大子串长度就要减去,所以此时不能动i,要动j
        /*
            以ABDBCAB,BDCAB举例子,从后往前走,CAB都是可以被保存下去的,所以我们就不管CAB了,
            且i和j会一路向前,则字符串变为ABDB,BD,此时i和j都指向最后一个字母
            dp[i-1][j]意思是:ABD和BD的最大子串长度,为2
            dp[i][j-1]意思是:ABDB和B的最大子串长度,为1
            这就说明,减去的那一个字母s2[j]是非常重要的,是子串的组成之一
            所以就不能让j--,而是i--
            如果上面的比较反过来,也就是本else if的情况,那么就说明s[i]很重要,是子串的组成之一
            就不能让i--,而是j--
        */
        {
            j--;
        }
        else if (dp[i-1][j]>dp[i][j-1])
        //同理上面
        {
            i--;
        }
        else
        /*
            此时的else其实只剩下一个可能性,dp[i-1][j]==dp[i][j-1],当两个数字相等的时候
            举个例子为ABDC和ABCD,可以很轻松得出结果,最大子串长度为3,子串可以为ABD或ABC
            dp[i-1][j]意思为:ABD和ABCD的最大子串长度,为3
            dp[i][j-1]意思为:ABDC和ABC的最大子串长度,为3
            二者相等,此时看我们的例子可知道,子串开始有不同解了,所以要进行递归,而且要传递str的值
            因为str保存了相同后缀,举个例子ABDCEF和ABCDEF,最大长度为5
            子串为ABDEF或ABCEF,可以知道,str在 dp[i-1][j]==dp[i][j-1]之前已经保存了EF两个字母,
            他们也正是答案的相同后缀,所以要递归,让他们走不同的路
        */
        {
            rec_save_str(i-1,j,str);
            rec_save_str(i,j-1,str);
            return;
        }
    }
    if(str.length()) res.insert(str);
}

int main()
{
    cin >> s1 >> s2;
    int m = s1.size();
    int n = s2.size();

    //生成dp矩阵
    int son_len_max=max_len(m, n);

    //生成结果
    string str;
    rec_save_str(m,n,str);

    //输出结果 
    /*
        这里选择set
        1、是因为set可以自动排序
        2、是因为在rec_save_str(m,n,str)过程中会有重复的子串插入,所以也做了去重
    */
    set<string>::iterator it=res.begin();
    if(res.empty())
        cout<<"NO"<<endl;
    else
    {
        for(;it!=res.end();it++)
            cout<<*it<<endl;
    }
    return 0;
}
  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值