LeetCode | 2023.03.28 | 1092. 最短公共超序列

"""
題目分析:
    這是困難題,實際上題目要求解的是最短字符串(假設爲str),其中str1和str2都是x的子字符串
    str1=   a b a c
    str2= c a b
    seq=  c a b a c
理論1:遞歸,結果超時
    從後面開始比較兩個字符串的字符,
    分兩種情況進行討論
    1.如果str1[-1]=str2[-1];那麼遞歸進行後面比較
    2.如果str1[-1]<>str2[-1];比較兩者長度更小的字符串
    2.1 計算an1=str1[:-1]和str2進行比較
    2.2 計算an2=str1和str2[:-1進行比較
    2.3 計算長度較小者,返回ans+str[-1]
理論2:dp
    1.首先獲得兩個字符串的最長子序列長度,記錄dp
        str1=abac;str2=cab;最長公共子序列=ab
        dp[i][j]表示str1[0:i]和str2[0:j]的最長公共子序列
       m\n   0   1   2   3
        0                
        1          \a    a
        2           a   \ab
        3           a   \ab
        4       \c  c   \ab
    2.從後往前反向進行循環,將缺失的字符補齊,這樣就得到最長子序列字符串
    2.1 如果str1[i]=str2[j],ans+str1[i];指針同時向後
    2.2 如果是str1特有字符,ans+str1[i];i向後
    2.3 如果是str2特有字符,ans+str2[j];j向後
    2.4 最後將未處理完成的str1或str2
注意/難點:
    遞歸:處理str1或str2爲空情況
    dp:邊界從1開始
"""
from functools import lru_cache
import numpy as np
class Solution:
    #DFS遞歸
    def shortestCommonSupersequence_DFS(self, str1: str, str2: str) -> str:
        m=len(str1)-1                                     #str1指針初始位置
        n=len(str2)-1                                     #str2指針初始位置
        @lru_cache(None)             #緩存裝飾器,記憶化DFS結果
        def DFS(i,j):                    #i,j分別表示str1和str2的指針下標
            print("a:"+str1[:i+1],"b:"+str2[:j+1])      #debug.only
            if i<0:return str2[:j+1]            #str1爲空,返回str2剩餘部分
            if j<0:return str1[:i+1]            #str2爲空,返回str1剩餘部分
            if str1[i]==str2[j]:                #如果尾部字符相等
                return DFS(i-1,j-1)+str1[i]     #遞歸進行前一字符比較
            #如果尾部字符不同
            ans1=DFS(i-1,j)                     #str1往前一位
            ans2=DFS(i,j-1)                     #str2往前一位
            # print("ans1:",ans1)
            # print("ans2:",ans2)
            if len(ans1)<len(ans2):             #比較更少長度字符串
                return ans1+str1[i]             #組成返回字符串
            else:
                return ans2+str2[j]             #組成返回字符串
        ret=DFS(m,n)                                       #開始遞歸
        return ret                                         #返回結果

    #dp運態規劃
    def shortestCommonSupersequence_dp(self, str1: str, str2: str) -> str:
        m=len(str1)
        n=len(str2)
        dp=[[0]*(n+1) for _ in range(m+1)]                 #生成dp記錄長度
        tb=[[""]*(n+1) for _ in range(m+1)]                 #生成tb記錄字符串
        # dp[i][j]表示str1[0:i]和str2[0:j]的最長公共子序列
        for i in range(1,m+1):
            for j in range(1,n+1):
                if str1[i-1]==str2[j-1]:
                    dp[i][j]=dp[i-1][j-1]+1
                    tb[i][j]=tb[i-1][j-1]+str1[i-1]    #debug
                else:
                    dp[i][j]=max(dp[i-1][j],dp[i][j-1])
                    #debug
                    if len(tb[i-1][j])>len(tb[i][j-1]):
                        tb[i][j]=tb[i-1][j]
                    else:
                        tb[i][j]=tb[i][j-1]
        print(np.array(dp))                      #debug
        print(np.array(tb))                      #debug
        #反向循環,補齊字符串
        ret=''                                           #初始化結果字符串
        i,j=m,n                                          #初始化指針
        while i>0 and j>0:                               #開始反向循環
            #兩者共有字符
            if str1[i-1]==str2[j-1]:                     #字符串相同
                ret=str1[i-1]+ret                        #拼接字符串
                i-=1
                j-=1
            #如果是str1特有字符
            elif dp[i][j]==dp[i-1][j]:
                ret=str1[i-1]+ret                        #拼接字符串
                i-=1
            #如果是str2特有字符
            elif dp[i][j]==dp[i][j-1]:
                ret=str2[j-1]+ret                        #拼接字符串
                j-=1
        #處理未完成的str1或str2
        if i>0:
            ret=str1[0:i]+ret
        if j>0:
            ret=str2[0:j]+ret
        return ret

str1 = "abac"
str2 = "cab"
# ans=Solution().shortestCommonSupersequence_DFS(str1,str2)
ans=Solution().shortestCommonSupersequence_dp(str1,str2)
print(ans)

dp數據相關的展示

      c a b
  dp 0 1 2 3
  0 0      
a 1   0 1 1
b 2     1 2
a 3     1 2
c 4   1 1 2

反向補齊字符串 

i j str1 str2 dp[i][j] dp[i-1][j] dp[i][j-1] ret
4 3 c b 2 2 1 c
3 3 a b 2 2 1 ac
2 3 b b 2 1 1 bac
1 2 a a 1 0 0 abac
0 1 a c \ \ \ cabac
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章