最长公共子序列问题[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]的值。好,让我们进行填空。
最长公共子序列问题[C++版]_第1张图片
1.如上图所示,我们看一下两个星星所标记的行和列,我们先不管已经填好的这些值,假定我们已经填好了这些,我们下一步怎么填问号的内容呢?
简单的很!自己判断嘛!子串"abc"和子串"acb"的公共子序列长度是啥啊?“ab"或者"ac"吧?我们先不管究竟是"ab"还还是"ac,先填上长度2”。
最长公共子序列问题[C++版]_第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?可以理解为两个字符串,其中一个若为空字符串,还有个锤子的公共子序列啊~
最长公共子序列问题[C++版]_第3张图片
3.最后来一波总结~如上图所示,我们知道
dp[i][j]**如何确定了吧!
还是很有意思的吧!下面上我的代码。

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

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

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

结果如下:
最长公共子序列问题[C++版]_第5张图片
加油! 为了未来!

你可能感兴趣的:(C++,动态规划,最长公共子序列,C++,动态规划,模板题目,最长公共子序列,递归)