这篇文章主要分析三个同类型问题,最长公共子串,最长公共子序列以及字符串的编辑距离。这三个问题都涉及到二维动态规划,统一整理一下。
1.最长公共子串问题
解析:假设字符串s1长度为m,字符串s2长度为n,思路是构建一个(m+1)*(n+1)大小的矩阵dp,dp[i][j]代表以s1中第i-1个字符串结尾,s2中第j-1个字符串结尾时最长子串的长度,有了dp[i][j]的含义,我们就可以定义状态方程:
![9deb4ddfa19f3274ca08553aa4341e65.png](https://i-blog.csdnimg.cn/blog_migrate/fbe8e7eb3de6e64a2a1e7d894634aad6.jpeg)
下面举一个栗子。
s1 = ‘abcd’,s2=‘acd’,最长公共子串为‘cd’,遍历后矩阵如下图。
![da94758ed98609d06e1ae843e8b9f3a1.png](https://i-blog.csdnimg.cn/blog_migrate/4b9b64e71d8bea3b1cd0fcc75c0ad8e5.jpeg)
当i,j其中任何一个为0,此时s1或者s2字符串还未开始遍历,LCS(最长公共子串)必然为空,因此第一行第一列全为0,再看dp[1][1],此时i指向‘b’,j指向‘c’,s1[0] = s2[0] = ‘a’,所以此时dp[i][j] = dp[i-1][j-1]+1,所以dp[1][1] = 1,意思是遍历至s1中‘a’结尾,s2中‘a’结尾时LCS为1.后面也是同样的规律,最终遍历完两个字符串,矩阵中最大的值就是最长公共子串长度,例子中矩阵最右下方的值最大,i=4,j=3,dp[4][3]=2,意思是遍历至s1中‘d’结尾,s2中‘d’结尾时LCS为2.注意此时恰好遍历到字符串最后,也有可能在字符串中出现最大值。
代码如下:
def LCS(s1, s2):
if not s1:
return len(s2)
if not s2:
return len(s1)
dp = [[0 for i in range(len(s2) + 1)] for i in range(len(s1) + 1)]
max_len = 0
for i in range(1, len(s1) + 1):
for j in range(2, len(s2) + 1):
if s1[i - 1] == s2[j - 1]:
dp[i][j] = dp[i - 1][j - 1] + 1
if dp[i][j] > max_len:
max_len = dp[i][j]
p = i
print (s1[p - max_len :p])
print (max_len)
2.最长公共子序列问题
公共子串必须是连续的,公共子序列则不要求,比如s1 = ‘abcdf’,s2=‘acdm’,则最长公共子序列为‘acd’,长度为3.
解析:假设字符串s1长度为m,字符串s2长度为n,思路也是是构建一个(m+1)*(n+1)大小的矩阵dp,此时dp[i][j]代表s1中截止到第i个字符之前,s2中截止到第j个字符串之前,两个短序列中最长公共子序列长度。有了dp[i][j]的含义,我们就可以定义状态方程:
![a4e549d6ad83f1656bc09d2ecb566e3a.png](https://i-blog.csdnimg.cn/blog_migrate/46de053362d90fb34d228e03e00d18e4.jpeg)
栗子。
s1 = ‘abcdf’,s2=‘acdm’,最长公共子序列为‘acd’,遍历后矩阵如下图。
![37ec2a144b65ec1d73fc5f4de6b388df.png](https://i-blog.csdnimg.cn/blog_migrate/35eef965074f72352a48b8b67e972f67.jpeg)
当i,j其中任何一个为0,此时s1或者s2字符串还未开始遍历,LCS(最长公共子序列)必然为空,因此第一行第一列全为0,再看dp[1][1],此时i指向‘b’,j指向‘c’,s1[0] = s2[0] = ‘a’,所以此时dp[i][j] = dp[i-1][j-1]+1,所以dp[1][1] = 1,意思是遍历s1中‘b’之前所有字符,s2中‘c’之前所有字符,此时LCS为1.再看dp[1][2],此时i指向‘b’,j指向‘d’,s1[0] != s2[1] ,所以此时dp[i][j] = max(dp[i-1][j],dp[i][j-1]),所以dp[1][2]=1,意思是遍历s1中‘b’之前所有字符,s2中‘d’之前所有字符,此时LCS为1.后面也是同样的规律,最终遍历完两个字符串,矩阵中最后一个值就是最长公共子序列长度。
代码如下:
def LCS1(s1, s2):
if not s1 or not s2:
return 0
dp = [[0 for i in range(len(s2) + 1)] for j in range(len(s1) + 1)]
lcs = ''
for i in range(1, len(s1) + 1):
for j in range(1, len(s2) + 1):
if s1[i - 1] == s2[j - 1]:
dp[i][j] = dp[i-1][j-1] + 1
else:
dp[i][j] = max(dp[i-1][j], dp[i][j-1])
i = len(s1)
j = len(s2)
while i > 0 and j > 0:
if s1[i-1] == s2[j-1] and dp[i][j] == dp[i-1][j-1] + 1:
lcs = s1[i - 1] + lcs
i = i - 1
j = j - 1
continue
elif dp[i][j] == dp[i-1][j]:
i = i - 1
continue
elif dp[i][j] == dp[i][j-1]:
j = j - 1
continue
print (lcs)
print (dp[len(s1)][len(s2)])
3. 字符串的编辑距离(Leetcode 72.Edit Distance)
![52b65696f0ae8ead94d0b87e071af646.png](https://i-blog.csdnimg.cn/blog_migrate/fca020dd5c3067323a8cb346f64fb159.jpeg)
解析:同样的思路,不过稍微复杂一点,dp[i][j]代表s1中截止到第i个字符之前,s2中截止到第j个字符串之前,需要的最小编辑距离。状态方程如下:
![67783e319ea653f9083a337fa44135ea.png](https://i-blog.csdnimg.cn/blog_migrate/e17a0a82bd994be713518e7c4e2276bc.png)
这里强调一下,如果dp[i][j]=dp[i][j-1]+1,表示在dp[i][j-1]基础上对s1做了一次插入操作,如果dp[i][j]=dp[i-1][j]+1,表示在dp[i-1][j]基础上对s1做了一次珊瑚虫操作,如果dp[i][j]=dp[i-1][j-1]+1,表示在dp[i-1][j-1]基础上对s1做了一次替换操作。
栗子。
s1 = ‘horse’,s2=‘ros’,最短编辑距离为3,遍历后矩阵如下图。
![cdc97424fb9de64201e47b625757521b.png](https://i-blog.csdnimg.cn/blog_migrate/c6eb298902426fc01017cadb53ed573e.jpeg)
当i=0,j=1,此时要想由s1变为s2,只需一步操作,在s1末尾增加一个字符,即s2首字符,此时s1=s2,编辑距离为1,同理可得,矩阵第一行,第一列都是从0依次递增。再看dp[1][1],此时i指向‘o’,j指向‘o’,s1[0] !=s2[0],所以此时dp[i][j] = min(dp[i-1][j]+1,dp[i][j-1]+1,dp[i-1][j-1]+1),所以dp[1][1] = 1,意思是遍历s1中‘o’之前所有字符,s2中‘o’之前所有字符,需要最短编辑距离为1,即把s1中‘h’替换为‘r’,进行的是替换操作.后面也是同样的规律,最终遍历完两个字符串,矩阵中最后一个值就是最短编辑距离。
代码如下:
class Solution(object):
def minDistance(self, word1, word2):
"""
:type word1: str
:type word2: str
:rtype: int
"""
if not word1:
return len(word2)
if not word2:
return len(word1)
dp = [[0 for i in range(len(word2) + 1)] for j in range(len(word1) + 1)]
for i in range(len(word1) + 1):
dp[i][0] = i
for i in range(len(word2) + 1):
dp[0][i] = i
for i in range(1, len(word1) + 1):
for j in range(1, len(word2) + 1):
if word1[i - 1] == word2[j - 1]:
dp[i][j] = dp[i - 1][j - 1]
else:
dp[i][j] = min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + 1)
return dp[len(word1)][len(word2)]
2019.4.15
百度二面就问了字符串编辑距离,面试官给我解释问题就解释了十分钟,凉凉...