最長公共子序列
問題描述
給定兩個字串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
更多有趣內容,請掃碼關注一波~