代码随想录算法训练营第四十五天 | 115.不同的子序列 583.两个字符串的删除操作 72.编辑距离

LeetCode 115.不同的子序列:

文章链接
题目链接:115.不同的子序列

思路:

分析题目可知要求的是,统计s的子序列中t出现的个数,其中子序列不是连续子序列。
动规五部曲:

  • ① dp数组及含义:
    dp[i][j]:以s[i - 1]结尾的子序列中含有 t[0:j](即[0, j - 1]的 t )的数量
  • ② 递推式
    首先依据s[i - 1] =? t[j - 1]来分类
    • s[i - 1] == t[j - 1]:采用s[i - 1]作为t子序列的一部分dp[i - 1][j - 1];不采用s[i - 1]作为t子序列的一部分dp[i - 1][j],然后dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j]。(假设s[0:i]是bagg,t[0:j]是bag,s[3] = t[2],含有的数量应该是bagg 和bag g两个,也就是采用s[i - 1]和不采用s[i - 1]的数量相加)
    • s[i - 1] != t[j - 1],那么就只有不采用s[i - 1]一种了,dp[i][j] = dp[i - 1][j]
  • ③ 初始化
    由递推式可知,dp[i][j]由左上方和上方推导得到,因此要初始化第0行和第0列。
    从dp数组的含义出发:
    dp[i][0]:即s[0: i]中删除字符出现空序列的数量,1
    dp[0][j] : 空序列中删除字符出现 t[0 : j]的数量,0
    dp[0][0]:空序列中删除字符出现空序列的数量,1
  • ④ 遍历方式
    由递推公式可知,dp[i][j]由两个蓝色部分推导得到,因此只要是在遍历到dp[i][j]时,两个蓝色部分已经被赋值的遍历方式均可。
    先行后列 or 先列后行(顺序遍历)
    在这里插入图片描述
  • ⑤ 举例:
    最后在dp[-1][-1]得到最终结果
    在这里插入图片描述
    代码
    代码需要注意的是,题目说了最终结果要模7+10^9,那么比较好的做法是,在遍历赋值的时候就将dp的结果进行模运算,而不是求最终结果时再模(可能其它语言的话,最后进行模,dp的值可能在模之前已经溢出了,也就是超出数据类型的范围了)
class Solution:
    def numDistinct(self, s: str, t: str) -> int:
        lens, lent = len(s), len(t)
        if lent <= 0:   # t 为空字符串
            return 1
        if lens <= 0:   # s 为空字符串
            return 0
        IMOD = 7 + 10**9    # 最终结果要对这个数取模
        # 初始化
        dp = [[0] * (lent + 1) for _ in range(lens + 1)]
        for i in range(lens + 1):   # 第0列置为1
            dp[i][0] = 1
        # 遍历
        for i in range(1, lens + 1):
            for j in range(1, lent + 1):
                if s[i - 1] == t[j - 1]:
                    dp[i][j] = (dp[i - 1][j - 1] + dp[i - 1][j]) % IMOD
                else:
                    dp[i][j] = dp[i - 1][j]
        return dp[lens][lent]
        

LeetCode 583.两个字符串的删除操作:

文章链接
题目链接:583.两个字符的删除操作

思路

动态规划1

动规五部曲

  • ① dp数组及含义:
    dp[i][j]:使word1[0, i - 1]和word2[0, j - 1]相同的最小步数
  • ② 递推式:
    首先根据word1[i - 1] =? word2[j - 1]来分类
    • word1[i - 1] == word2[j - 1],因为需要的是最小步数,因此可以默认相同的话保留为最终相同的字符,dp[i][j] = dp[i - 1][j - 1](或者换种方式理解,要么保留dp[i - 1][j - 1];要么不保留,那么就是删除一个字符:min(dp[i - 1][j] + 1, dp[i][j - 1] + 1),然后三个求min,实际用下来发现最小的都是dp[i - 1][j - 1])
    • word1[i - 1] != word2[j - 1]:那就是不保留结尾的字符,也就是word1或word2删除一个字符(实际上两个都删除的情况被包含在删除一个字符中了),dp[i][j] = min(dp[i - 1][j] + 1(删除操作的步数),dp[i][j - 1] + 1)
  • ③ 初始化
    由递推公式可知,dp[i][j]由下图中三个蓝色部分得到,因此需要初始化第0行和第0列。
    接着从dp数组的含义出发:
    dp[0][j]:即使空字符和word2[0, j - 1]相同的最小步数为 j (全删除)
    同理可得 dp[i][0]= i
    在这里插入图片描述
  • ④ 遍历方式:
    先行后列 / 先列后行,但要是顺序遍历
  • ⑤ 举例
    最后得到dp[-1][-1]的值为最终结果
    在这里插入图片描述
    代码
class Solution:
    def minDistance(self, word1: str, word2: str) -> int:
        len1, len2 = len(word1), len(word2)
        # word1或者word2中有空字符,另一个需要删除全部字符才能相同
        if len1 <= 0:
            return len2
        if len2 <= 0:
            return len1
        # 初始化
        dp = [[0] * (len2 + 1) for _ in range(len1 + 1)]
        # dp[0][j] = j, dp[i][0] = i
        for i in range(1, len1 + 1):
            dp[i][0] = i
        for j in range(1, len2 + 1):
            dp[0][j] = j
        # 遍历
        for i in range(1, len1 + 1):
            for j in range(1, len2 + 1):
                dp[i][j] = min(dp[i - 1][j] + 1, dp[i][j - 1] + 1)  # 删除一个字符
                if word1[i - 1] == word2[j - 1]:
                    dp[i][j] = min(dp[i - 1][j - 1], dp[i][j])  # 删除一个字符与不删除字符的最小步数
        return dp[len1][len2]
        
动态规划2

利用之前求最长公共子序列(非连续),
最小步数 = len(word1) + len(word2) - 长度*2
求最长公共子序列的动态规划:

  • dp数组及定义:
    dp[i][j] : word1[0, i - 1]和word2[0, j - 1]的最长公共子序列的长度
  • 递推公式
    • word1[i - 1] == word2[j - 1]:dp[i][j] = dp[i - 1][j - 1] + 1
    • word1[i - 1] != word2[j - 1]:dp[i][j] = max(dp[i][j - 1], dp[i - 1][j])
  • 初始化
    初始化为全0
  • 遍历方式与动态规划1相同
  • 举例
    举例可见前面求最长公共子序列的题(非连续子序列)
"""
求最长公共子序列(非连续)的长度,然后len(word1) + len(word2) - 长度*2 = 最小步数
"""
class Solution:
    def minDistance(self, word1: str, word2: str) -> int:
        len1, len2 = len(word1), len(word2)
        # word1或word2中有空字符串
        if len1 <= 0:
            return len2
        if len2 <= 0:
            return len1
        # 初始化
        dp = [[0] * (len2 + 1) for _ in range(len1 + 1)]    # 最长公共子序列的长度
        # 遍历
        for i in range(1, len1 + 1):
            for j in range(1, len2 + 1):
                if word1[i - 1] == word2[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 len1 + len2 - dp[len1][len2] * 2

LeetCode 72.编辑距离:

文章链接
题目链接:72.编辑距离

思路:

分析题目可知,可以对一个单词进行插入、删除和替换三种操作
动规五部曲

  • ① dp数组及含义:
    dp[i][j]:将word1[0, i - 1]转换成word2[0, j - 1]的最少操作数
  • ② 递推式:
    首先根据word1[i - 1] =? word2[j - 1]来分类。
    如果 = ,那么不需要编辑,dp[i][j] = dp[i - 1][j - 1]
    如果 !=,那么需要编辑,而编辑操作有三种:插入、删除和替换
    • 插入:需要理解的是,对word1插入一个字符==对word2删除一个字符。因此,插入操作是通过对word2删除一个字符达到的。也就是将word1[0, i - 1]转换为word2[0, j - 2]的最少操作数 + 1(删除word2[j - 1]),即dp[i][j] = dp[i][j - 1] + 1
    • 删除:不相等时删除word1的一个字符,即将word1[0, i - 2]转换为word2[0, j - 1]的最少操作数 + 1(删除word1[i - 1]),dp[i][j] = dp[i - 1][j] + 1
    • 替换:将word1[i - 1]替换为word2[j - 1],再求将word1[0, i - 2]转换为word2[j - 2]的最小操作数,dp[i][j] = dp[i - 1][j - 2] + 1。
      然后三个求min
  • ③ 初始化
    由递推式可得dp[i][j]由三个蓝色部分推导得到,因此需要初始化第0行和第0列。
    从dp数组的含义来理解
    dp[0][j]:将空字符串转换为word2[0, j - 1]的最小操作数,就是插入 j 个字符嘛,即 j
    dp[i][0]:将word1[0, i - 1]转换为空字符串的最小操作数,就是删除全部 i 个字符嘛,即 i
    在这里插入图片描述
  • ④ 遍历
    先行后列/先列后行, 需要是顺序遍历(不管先行还是先列)
  • ⑤ 举例
    最后的结果是dp[-1][-1]
    在这里插入图片描述
    代码
class Solution:
    def minDistance(self, word1: str, word2: str) -> int:
        len1, len2 = len(word1), len(word2)
        # word1或word2是空字符串
        if len1 <= 0:
            return len2
        if len2 <= 0:
            return len1
        # 初始化
        dp = [[0] * (len2 + 1) for _ in range(len1 + 1)]
        for i in range(1, len1 + 1):
            dp[i][0] = i
        for j in range(1, len2 + 1):
            dp[0][j] = j
        # 遍历
        for i in range(1, len1 + 1):
            for j in range(1, len2 + 1):
                if word1[i - 1] == word2[j - 1]:
                    dp[i][j] = dp[i - 1][j - 1]
                else:
                    dp[i][j] = min(dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]) + 1
        return dp[len1][len2]
        

学习收获:

不同的子序列:

  • 因为要求最多出现的次数,因此即使相等,也要s[i - 1]包括进去的次数 + 不将s[i - 1]包括进去的次数
    两个字符串的删除操作:
  • 方法1,思路类似上一题,但是相等的话不需要分类,不相等的话,因为两个字符串都可以删除,所以要分删哪个字符串
  • 方法2:删除最小次数 = 两个字符串的长度 - 最长公共非连续子序列长度 *2 ,转换为求最长公共非连续子序列长度
    编辑距离:
  • 本题的编辑操作有插入、删除和替换,因此不相等时还要分三种编辑操作,需要注意的是,在word1插入相当于在word2删除,从而转换为删除word2元素;以及删除word1元素和替换word1元素,最后三个求min
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值