最長公共子序列問題[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;
}

結果如下:
在這裏插入圖片描述
加油! 爲了未來!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章