最长公共子串LCS(Longest Common Substring)
给出两个字符串str1和str2,求他们的最长公共子串
- 利用dp[i][j]表示某公共子串的最大长度,这个公共子串在str1中以str1[i]结尾,在str2中以str2[j]结尾
- 依次比较str1与str2中的每一个字符str1[i]和str2[j]
- 若
str1[i]==str2[j]
,那么dp[i][j] = dp[i-1][j-1] + 1
,这意味着找到了长度更长的公共子串
(若两个字符串中,各自的前一个字符也相同,则dp[i-1][j-1]的值大于0,否则dp[i-1][j-1]为0) - 若
str1[i]!=str2[j]
,那么dp[i][j] = 0
- 遍历过程中,用
maxLen
不断记录并更新最长公共子串的长度,若需要输出子串内容,还需要另外用start1
或start2
记录子串在str1、str2中的起始坐标 - 实现时,为了防止dp访问越界,在dp表格的最左边和最上边各添加一行全0行,这样
str1[i]、str2[j]
改为对应dp[i+1]、[j+1]
(访问dp时,下标加1)
def longestSubStr(str1,str2):
"""查找str1、str2的最长公共子串,
返回最长公共子串、该子串在str1的起始下标、在str2的起始下标
若没有公共子串,返回'', 0, 0"""
len1 = len(str1)
len2 = len(str2)
maxLen,start1,start2 = 0,0,0#用于更新当前的最长公共子串
dp = [[0 for _ in range(len2+1)]for _ in range(len1+1)]
for i in range(len1):
for j in range(len2):
#iadd1,jadd1=i+1,j+1
if str1[i] == str2[j]:
dp[i+1][j+1] = dp[i][j]+1
## else:
## dp[i+1][j+1] = 0
if (maxLen < dp[i+1][j+1]):#更新当前的最长公共子串
maxLen = dp[i+1][j+1]
start1 = i-maxLen+1
start2 = j-maxLen+1
return str1[start1:start1+maxLen],start1,start2
================= RESTART: C:\Users\13272\Desktop\最长公共子串LCS.py =================
>>> longestSubStr('fish','hisa')
('is', 1, 1)
>>> longestSubStr('a','bcd')
('', 0, 0)
最长公共子序列LCS
给出两个序列x,y,找出他们的最长公共子序列LCS(Longest Common Subsequence)
与最长公共子串的区别在于,子串必须是多个相连的字符,而子序列不一定
LeetCode 1143. 最长公共子序列(LongestCommonSubsequence)
给出两个序列x,y,找出他们的最长公共子序列LCS的长度
如s1=‘abcde’,s2=‘aceb’,返回3
思路:
- 状态:s1的一个子串与s2的一个子串的LCS
选择:对于s1/s2一个新字符,可以选择将其纳入考虑,看是否有更长的LCS - 用
dp[i][j]
表示子串x[0:i]
和子串y[0:j]
的最长公共子序列长度
套路:两个字符串的动态规划,一般都需要二维dp数组,i、j分别与两个字符串挂钩
- 如果是“连续”的子串问题,i与以s1[i]结尾的子串挂钩
- 若果是“不连续”的子序列问题,i与s1[0…i]的子序列挂钩
- 找状态转移方程
数学归纳思想,假定已知dp[0]...dp[i-1]
,怎么求dp[i]
:对于字符s[i]
,可以将其拼接到其他递增序列上,求其中的最大值
转移方程:
- 若
x[i]==y[j]
,那么dp[i][j] = dp[i-1][j-1] + 1
,这意味*找到了长度更长的公共子序列 - 若
str1[i]!=str2[j]
,那么dp[i][j] = max(dp[i-1][j],dp[i][j-1])
分别比较没有这两个字符后的LCS(子串x[0:i-1]
和y[0:j]
的LCS or 子串x[0:i]
和y[0:j-1]
的LCS,选择较大的那一个)
实现:
- 技巧:为防止状态转移方程中出现越界,在dp表格的最左边和最上边各添加一行全0行,这样
dp[i]、[j]
改为对应x[i-1]、y[j-1]
(访问dp时,下标加1) - maxLen、flag用于不断更新最长公共子序列和输出这个序列
- 若需要打印LCS内容,额外使用flag数组记录当前的dp[i][j]是从左上/左/上来的,反推回去然后打印
def printLcs(flag,a,i,j):
global ans
if i==0 or j==0:
return
if flag[i][j]=='OK':
printLcs(flag,a,i-1,j-1)
ans.append(a[i-1])
elif flag[i][j]=='Left':
printLcs(flag,a,i,j-1)
else:
printLcs(flag,a,i-1,j)
def longSubSeq(str1,str2):
len1 = len(str1)
len2 = len(str2)
maxLen = 0
c = [[0 for i in range(len2+1)]for i in range(len1+1)]
flag = [[0 for i in range(len2+1)]for i in range(len1+1)]
for i in range(len1+1):
for j in range(len2+1):
if i == 0 or j == 0:
c[i][j] = 0
elif str1[i-1] == str2[j-1]:
c[i][j] = c[i-1][j-1]+1
flag[i][j] = 'OK'# 从左上来的
maxLen = max(maxLen,c[i][j])
elif c[i][j-1] > c[i-1][j]:
c[i][j] =c[i][j-1]
flag[i][j] = 'Left'# 从左边来的
else:
c[i][j] =c[i-1][j]
flag[i][j] = 'UP'# 从上面来的
#需要获得最长公共子序列的内容时,创建全局变量ans并调用此函数
printLcs(flag,str1,len1,len2)
return maxLen
a='ABCBDAB'
b='BDCABA'
ans = []
maxLength = longSubSeq(a,b)
================= RESTART: C:\Users\13272\Desktop\最长公共序列LCS.py =================
>>> ans
['B', 'C', 'B', 'A']
>>> maxLength
4