"""
題目分析:
這是困難題,實際上題目要求解的是最短字符串(假設爲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 |