题目:最长公共子序列(子串) ---- LCS
解法:动态规划
思路:设dp[i][j]
为以第一个字符串的前i
个,第二个字符串的前j
个字符构成的最长公共子串,设这两个字符串分别为A
和B
,则当A[i]==B[j]
时,dp[i][j]=dp[i-1][j-1]+1
;若A[i]!=B[j]
,则dp[i][j]=max(dp[i-1][j],dp[i][j-1])
。
此处解释一下当 A[i]!=B[j]
时,dp[i][j]= max(dp[i-1][j],dp[i][j-1])
的意义:
即当 A[i]!=B[j]
时,取A[0:i-1]与B[0:j],A[0:i]与B[0:j-1]
这两种情况下公共子串更长的那一种。
代码
def LCS(A,B):
length1 = len(A)
length2 = len(B)
if length1 == 0 or length2 == 0:
return 0
dp = [[0 for j in range(length2+1)] for i in range(length1+1)]
for i in range(1,length1+1):
for j in range(1,length2+1):
if A[i-1] == B[j-1]:
dp[i][j] = dp[i-1][j-1] + 1
else:
dp[i][j] = max(dp[i-1][j],dp[i][j-1])
return dp[-1][-1]
变形一 要求记录最长的那个序列
为记录路径(即获得那个子序列),用一个2维矩阵path
,首先path全初始化为0,然后在更新dp
的同时按以下规则更新path
:
path[i][j]=1
ifA[i]==B[j]
path[i][j]=2
ifA[i]!=B[j] and dp[i-1][j]>=dp[i][j-1]
path[i][j]=3
ifA[i]!=B[j] and dp[i-1][j]<dp[i][j-1]
回溯:
从path
的最后一个元素开始,判断path[i][j]
的值,
若为1,则往[i-1][j-1]
继续回溯,回溯完成后记录A[i-1]
的值或B[j-1]
的值;(在回溯完成后记录保证了得到的子序列和原来的顺序相同,而不是倒序的)
若为2,则往[i-1][j]
继续回溯;
若为3,则往[i][j-1]
继续回溯;
代码如下:
def LCS(A,B):
length1 = len(A)
length2 = len(B)
if length1 == 0 or length2 == 0:
return 0,[]
dp = [[0 for j in range(length2+1)] for i in range(length1+1)]
path = [[0 for j in range(length2+1)] for i in range(length1+1)]
for i in range(1,length1+1):
for j in range(1,length2+1):
if A[i-1] == B[j-1]:
dp[i][j] = dp[i-1][j-1] + 1
path[i][j] = 1
else:
if dp[i-1][j]>=dp[i][j-1]:
dp[i][j] = dp[i-1][j]
path[i][j] = 2
else:
dp[i][j] = dp[i][j-1]
path[i][j] = 3
#common_substr = ''
# 用一个列表,同时适配序列和字符串两种情况
res = []
def back_track(row,col):
if row == 0 or col == 0:
return
if path[row][col] == 1:
# res.append(A[row-1]) # 放在这里的话,得到的序列是倒序的
back_track(row-1,col-1)
res.append(A[row-1])
elif path[row][col] == 2:
back_track(row-1,col)
else:
back_track(row,col-1)
back_track(length1,length2)
return dp[-1][-1],res
变形二 要求连续,即最长公共连续子序列
状态转移:如果A[i]==B[j]
,dp[i][j]=dp[i-1][j-1]+1
;否则,dp[i][j]=0
,然后记录dp[i][j]
的最大值即可。
代码
def LCCS(A,B):
length1 = len(A)
length2 = len(B)
if length1 == 0 or length2 == 0:
return 0
dp = [[0 for j in range(length2+1)] for i in range(length1+1)]
max_val = 0
for i in range(1,length1+1):
for j in range(1,length2+1):
if A[i-1] == B[j-1]:
dp[i][j] = dp[i-1][j-1] + 1
max_val = max(max_val,dp[i][j])
return max_val
变形二之变形,要求记录最长的连续子串。
思路:可以在更新max_val
的时候同时记录A
的索引或B
的索引,此处以记录A
的索引为例,假设最后一次更新max_val
时的索引max_index=i
,则A[max_index-max_val:max_index]
即为公共子串。代码如下:
def LCCS(A,B):
length1 = len(A)
length2 = len(B)
if length1 == 0 or length2 == 0:
return 0
dp = [[0 for j in range(length2+1)] for i in range(length1+1)]
max_val = 0
max_index = 0
for i in range(1,length1+1):
for j in range(1,length2+1):
if A[i-1] == B[j-1]:
dp[i][j] = dp[i-1][j-1] + 1
if dp[i][j]>max_val:
max_val = dp[i][j]
max_index = i
return max_val,A[max_index-max_val:max_index]