329. 矩阵中的最长递增路径c_最长公共子串系列问题

这篇文章主要分析三个同类型问题,最长公共子串,最长公共子序列以及字符串的编辑距离。这三个问题都涉及到二维动态规划,统一整理一下。

1.最长公共子串问题

解析:假设字符串s1长度为m,字符串s2长度为n,思路是构建一个(m+1)*(n+1)大小的矩阵dp,dp[i][j]代表以s1中第i-1个字符串结尾,s2中第j-1个字符串结尾时最长子串的长度,有了dp[i][j]的含义,我们就可以定义状态方程:

9deb4ddfa19f3274ca08553aa4341e65.png

下面举一个栗子。

s1 = ‘abcd’,s2=‘acd’,最长公共子串为‘cd’,遍历后矩阵如下图。

da94758ed98609d06e1ae843e8b9f3a1.png

当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

栗子。

s1 = ‘abcdf’,s2=‘acdm’,最长公共子序列为‘acd’,遍历后矩阵如下图。

37ec2a144b65ec1d73fc5f4de6b388df.png

当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

解析:同样的思路,不过稍微复杂一点,dp[i][j]代表s1中截止到第i个字符之前,s2中截止到第j个字符串之前,需要的最小编辑距离。状态方程如下:

67783e319ea653f9083a337fa44135ea.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

当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

百度二面就问了字符串编辑距离,面试官给我解释问题就解释了十分钟,凉凉...

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值