最長公共子序列你學會了嗎

演算法推薦管發表於2021-10-12

最長公共子序列

問題描述

給定兩個字串str1和str2,輸出兩個字串的最長公共子序列。如果最長公共子序列為空,則返回"-1"。目前給出的資料,僅僅會存在一個最長的公共子序列。

示例:

輸入:"abcde","ace"

輸出:"ace"

分析問題

首先,我們先來看一下什麼是子序列。一個字串的子序列是指這樣一個新的字串:它是由原字串在不改變字元的相對順序的情況下刪除某些字元(也可以不刪除任何字元)後組成的新字串。例如,“ace”是“abcde”的子序列,但是“aec”不是“abcde”的子序列。這個問題和求最長公共子串類似,我們也可以採用動態規劃的方法來求解。

我們假設str1[0,i-1]str2[0,j-1]的最長公共子序列為dp[i-1] [j-1],那麼str1[0,i]str2[0,j]的最長公共子序列等於多少呢?這主要取決於字元str1[i]str2[j]是否相等,如果相等,則最長公共子序列會加1,即dp[i] [j] = dp[i-1] [j-1] + 1。如果不相等,則dp[i] [j] = max(dp[i-1] [j],dp[i] [j-1])。有了動態轉移方程,我們只需要遍歷字串,逐步填寫狀態轉移矩陣就好了。

下面我們來看一下程式碼如何實現。

def longestCommonSubsequence(str1,str2):
    n=len(str1)
    m=len(str2)
    dp=[[0 for _ in range(n)] for _ in range(m)]

    #處理邊界條件
    if str1[0]==str2[0]:
        dp[0][0] = 1

    for i in range(n):
        if(str2[0]==str1[i]):
            dp[0][i]=1
        else:
            dp[0][i]=dp[0][i-1]

    for j in range(m):
        if(str1[0]==str2[j]):
            dp[j][0] = 1
        else:
            dp[j][0] = dp[j-1][0]


    for i in range(1, m):
        for j in range(1, n):
            if str2[i]==str1[j]:
                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-1][n-1]

由於題目要求是輸出具體的子序列,所以我們還需要根據狀態轉移矩陣逆推回去。

通過狀態轉移方程,我們可以知道dp[i] [j]要麼從dp[i-1] [j-1] +1 求得,要麼通過dp[i] [j-1] 和 dp[i-1] [j]的最大值得到,所以我們可以從dp[m-1] [n-1] 倒推出str1和str2的最大公共子序列。如下圖所示。

我們來看一下完整的程式碼實現。

def LCS(self , str1 , str2 ):
        n=len(str1)
        m=len(str2)
        if n==0 or m==0:
           return -1
        dp=[[0 for _ in range(n)] for _ in range(m)]

        #處理邊界條件
        if str1[0]==str2[0]:
            dp[0][0] = 1

        for i in range(n):
            if(str2[0]==str1[i]):
                dp[0][i]=1
            else:
                dp[0][i]=dp[0][i-1]

        for j in range(m):
            if(str1[0]==str2[j]):
                dp[j][0] = 1
            else:
                dp[j][0] = dp[j-1][0]


        for i in range(1, m):
            for j in range(1, n):
                if str2[i]==str1[j]:
                    dp[i][j] = dp[i - 1][j - 1] + 1
                else:
                    dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])


        #通過狀態轉移矩陣倒推出子序列
        tmp=[]
        s1_index=n-1
        s2_index=m-1
        while s1_index !=0 and s2_index !=0:

            if str1[s1_index]==str2[s2_index]:
                tmp.append(str1[s1_index])
                s1_index=s1_index-1
                s2_index=s2_index-1
            else:
                if dp[s2_index-1][s1_index]>dp[s2_index][s1_index-1]:
                    s2_index = s2_index - 1
                else:
                    s1_index = s1_index - 1

        #單獨處理第0行0列
        if s1_index==0:
            while s2_index>=0:
                if str1[0]==str2[s2_index]:
                    tmp.append(str1[0])
                    break
                s2_index=s2_index-1

        else:
            while s1_index>=0:
                if str1[s1_index]==str2[0]:
                    tmp.append(str1[s1_index])
                    break
                s1_index=s1_index-1
        if len(tmp)==0:
            return -1
        else:
            return "".join(tmp[::-1])

該演算法的時間複雜度和空間複雜度都是O(n^2)。

最後

送大家幾本比較不錯的演算法書籍~

小爭哥資料結構與演算法
連結:https://pan.baidu.com/s/19Jk_G_-QTnGb3GRyzbENgA

密碼:keis

谷歌大佬LeetCode刷題指南
連結:https://pan.baidu.com/s/1vtRIsVltTxmIioqqkeSS5g

密碼:r3xg

演算法小抄
連結:https://pan.baidu.com/s/1rU_T6GRZ-WmV9QFmnJfCBg

密碼:unh5

更多有趣內容,請掃碼關注一波~

相關文章