最长公共子序列问题[C++版]

最长公共子序列问题[C++版]

最长公共子序列问题

问题描述

最近适逢秋招,于是刷了一些题。腾讯的某道题完全没有思路,看到的网上解析遂发觉了这么个经典的“模板问题”。

输入: String_1: abcdaf String_2: acbcf
输出:最长公共子序列(abcf)或长度(4)

思路方法

别的方法就不进行赘述了。
首先根据两个字符串的长度m,n生成一个(m + 1)x (n + 1)的数组dp,并且令dp[m][0] = 0,dp[0][n] = 0。为什么这么做呢,这里来解释一下:
我这里借鉴了一下B站上某个外国人的做法。我们先这样做,然后依靠我们人为的去进行判断,主要目的是为了填空。动态规划是以空间换时间的利器吧,有了上个状态,经过我们的状态转移方程就可以推的下一阶段的状态。在这儿,每次操作的状态其实就是dp[i][j]的值。好,让我们进行填空。
在这里插入图片描述
1.如上图所示,我们看一下两个星星所标记的行和列,我们先不管已经填好的这些值,假定我们已经填好了这些,我们下一步怎么填问号的内容呢?
简单的很!自己判断嘛!子串"abc"和子串"acb"的公共子序列长度是啥啊?“ab"或者"ac"吧?我们先不管究竟是"ab"还还是"ac,先填上长度2”。
在这里插入图片描述
2. 就这样,我手动的全部填完了。
然后呢,我们分析一下有没有什么规律?如上图所示,我们已知结果为abcf,怎么得到的呢?或者说能不能用某个规律来说明我是怎么填好每个空的?(当然我们的大脑比较厉害,反正会填,不会的估计题都没看明白或者出题的真的是非人类了)
结论:当两个子串逐字符进行比较的时候,若发现了相同的字符,则此刻的dp[i][j] = dp[i - 1][j - 1] + 1。除此之外,dp[i][j]就看它上面的值和左边的值哪个大了,把大的值赋给它。
注意!我们虽然写的是dp[i][j],但实际比较的是String1[i-1]和String2[j-1]。为啥呢?看图吧,当我填dp[i][j] = 1的时候,是因为我发现了String1[0] = String2[0] = "a"的时候吧?而此时的dp[i][j]中的i和j都等于1。现在多少明白为什么定义(m + 1)x(n + 1)了吧?为什么预先设定了哪些0?可以理解为两个字符串,其中一个若为空字符串,还有个锤子的公共子序列啊~
在这里插入图片描述
3.最后来一波总结~如上图所示,我们知道
dp[i][j]**如何确定了吧!
还是很有意思的吧!下面上我的代码。

#include <string>
#include <algorithm>
using namespace std;

// 声明两个字符串s1,s2
string s1, s2;

int main() {
	// 输入
    cin >> s1;
    cin >> s2;
   
    // 获取两个字符串的长度
    int m = s1.size();
    int n = s2.size();
    
    // 声明动态规划数组 
    int dp[n+1][m+1];
    // 填0 初始化
    for(int i = 0; i < n+1; i++){dp[i][0] = 0;}
    for(int i = 0; i < m+1; i++){dp[0][i] = 0;}
    
    // 根据设定的规则一次填入dp[i][j],其中i, j >= 1
    for(int i = 1; i < n+1; i++){
        for(int j = 1; j < m+1; j++){
            if(s1[j-1] == s2[i-1]){
                dp[i][j] = dp[i-1][j-1] + 1;
            }else if(dp[i][j-1] > dp[i-1][j]){
                dp[i][j] = dp[i][j-1];
            }else{
                dp[i][j] = dp[i-1][j];
            }
        }
    }
    
    //打印填好的dp数组
    for(int i = 1; i < n+1; i++){
        for(int j = 1; j < m+1; j++){
            cout << dp[i][j] << " ";
        }
        cout << endl;
    }
    
    // 输出最长公共子序列的长度,就是二维数组dp的最后一个元素
    cout << dp[n][m] << endl;

    return 0;
}

输出结果:
在这里插入图片描述
4.如何得到最长公共子串?
添加了一个辅助数组来完成这项工作。这里用递归的方法求解并打印。从dp数组的最后一个值开始,然后根据规则往回找,其实dp[i][j]的值来源就三个吧?要么dp[i-1][j-1],要么dp[i][j-1],要么dp[i-1][j]。所以函数的实现就比较简单了,每次递归按照具体的情况找就行了,下面是我的代码。

#include <string>
#include <algorithm>
using namespace std;

string s1, s2;
// 定义二维数组用于辅助记录
int record[1000][1000];

//打印最长公共子串子串
void PrintLCS(int m, int n){
    if(n == 0 || m == 0){
        return;
    }
    if(record[n][m] == 1){
        PrintLCS(m-1, n-1);
        cout << s1[m-1];
    }else if(record[n][m] == 2){
        PrintLCS(m-1, n);
    }else{
        PrintLCS(m, n-1);
    }
}

int main() {
    cin >> s1;
    cin >> s2;
   
    int m = s1.size();
    int n = s2.size();
    
    int dp[n+1][m+1];

    for(int i = 0; i < n+1; i++){dp[i][0] = 0;}
    for(int i = 0; i < m+1; i++){dp[0][i] = 0;}

    for(int i = 1; i < n+1; i++){
        for(int j = 1; j < m+1; j++){
            if(s1[j-1] == s2[i-1]){
                dp[i][j] = dp[i-1][j-1] + 1;
                // 若dp[i][j]的值来源于dp[i-1][j-1]+1,则给同位置的record数组赋值为1
                record[i][j] = 1;
            }else if(dp[i][j-1] > dp[i-1][j]){
                dp[i][j] = dp[i][j-1];
                //若来源于dp[i][j-1],则给同位置的record数组赋值为2
                record[i][j] = 2;
            }else{
                dp[i][j] = dp[i-1][j];
                //若来源于dp[i-1][j],则给同位置的record数组赋值为3
                record[i][j] = 3;
            }
        }
    }

    for(int i = 1; i < n+1; i++){
        for(int j = 1; j < m+1; j++){
            cout << dp[i][j] << " ";
        }
        cout << endl;
    }

    cout << dp[n][m] << endl;

    PrintLCS(m, n);

    return 0;
}

结果如下:
在这里插入图片描述
加油! 为了未来!

  • 13
    点赞
  • 63
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
以下是最长公共子序列问题C++ 代码实现: ```cpp #include <iostream> #include <cstring> using namespace std; int LCS(string str1, string str2, int m, int n) { int dp[m + 1][n + 1]; for (int i = 0; i <= m; i++) { for (int j = 0; j <= n; j++) { if (i == 0 || j == 0) { dp[i][j] = 0; } else if (str1[i - 1] == str2[j - 1]) { 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]; } int main() { string str1 = "AGGTAB"; string str2 = "GXTXAYB"; int m = str1.size(); int n = str2.size(); cout << "Length of LCS: " << LCS(str1, str2, m, n) << endl; return 0; } ``` 在这个实现中,我们定义一个二维数组 `dp` 来存储最长公共子序列的长度。我们使用动态规划来解决问题。对于每个字符 `str1[i-1]` 和 `str2[j-1]`,我们可以有两种选择: 1. 如果它们相等,则它们一定属于最长公共子序列,因此我们将它们添加到最长公共子序列中,并将长度加一。此时,我们需要在 `str1[0:i-2]` 和 `str2[0:j-2]` 中找到最长公共子序列。 2. 如果它们不相等,则它们不能同时出现在最长公共子序列中。这意味着我们需要找到 `str1[0:i-2]` 和 `str2[0:j-1]` 或 `str1[0:i-1]` 和 `str2[0:j-2]` 中的最长公共子序列。 我们使用 `dp[i][j]` 存储 `str1[0:i-1]` 和 `str2[0:j-1]` 的最长公共子序列的长度。我们可以通过填充 `dp` 数组来计算最长公共子序列的长度。具体来说,对于任意 `i` 和 `j`,如果 `str1[i-1]` 和 `str2[j-1]` 相等,则 `dp[i][j]` 等于 `dp[i-1][j-1] + 1`,否则 `dp[i][j]` 等于 `max(dp[i-1][j], dp[i][j-1])`。最终,我们将返回 `dp[m][n]`,其中 `m` 和 `n` 是 `str1` 和 `str2` 的长度。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值