代码随想录训练营 Day45打卡 动态规划 part12 115. 不同的子序列 583. 两个字符串的删除操作 72. 编辑距离

代码随想录训练营 Day45打卡 动态规划 part12

一、力扣115. 不同的子序列

给你两个字符串 s 和 t ,统计并返回在 s 的 子序列 中 t 出现的个数,结果需要对 109 + 7 取模。
示例
输入:s = “rabbbit”, t = “rabbit”
输出:3
解释
如下所示, 有 3 种可以从 s 中得到 “rabbit” 的方案。
rabbbit
rabbbit
rabbbit

  1. 确定 dp 数组的定义
    dp[i][j] 表示 s 的前 i 个字符中子序列等于 t 的前 j 个字符的个数。

  2. 状态转移方程
    当 s[i-1] == t[j-1] 时,有两种情况:
    (1)使用 s[i-1] 来匹配:那么匹配个数等于 dp[i-1][j-1],即不考虑当前 s 和 t 的最后一个字符,子序列的数量。
    (2)不使用 s[i-1] 来匹配:匹配个数等于 dp[i-1][j],即仅用 s 的前 i-1 个字符的子序列中 t 的前 j 个字符出现的次数。
    状态转移方程:dp[i][j] = dp[i-1][j-1] + dp[i-1][j]
    当 s[i-1] != t[j-1] 时,dp[i][j] 只能等于 dp[i-1][j],即不使用 s[i-1] 来匹配的情况。
    状态转移方程:dp[i][j] = dp[i-1][j]

  3. dp 数组的初始化
    dp[i][0] = 1:无论 s 有多少字符,当 t 为空字符串时,s 的子序列中等于 t 的个数为 1(即空子序列)。
    dp[0][j] = 0:当 s 为空字符串但 t 不为空时,子序列中等于 t 的个数为 0,因为空字符串无法匹配非空字符串。

  4. 确定遍历顺序
    由于 dp[i][j] 依赖于 dp[i-1][j-1] 和 dp[i-1][j],所以遍历时需要从左到右,从上到下进行。

  5. 最终结果
    dp[len(s)][len(t)] 就是字符串 s 的子序列中 t 出现的次数。

代码实现

class Solution:
    def numDistinct(self, s: str, t: str) -> int:
        # 创建一个二维数组 dp,初始化为 0
        dp = [[0] * (len(t) + 1) for _ in range(len(s) + 1)]
        
        # 初始化 dp[i][0] = 1,因为 t 为空字符串时,子序列等于 t 的个数为 1
        for i in range(len(s) + 1):
            dp[i][0] = 1
        
        # 遍历字符串 s 和 t,填充 dp 数组
        for i in range(1, len(s) + 1):
            for j in range(1, len(t) + 1):
                if s[i-1] == t[j-1]:
                    # 如果 s[i-1] 和 t[j-1] 相等,dp[i][j] 由两部分组成:
                    # 1. dp[i-1][j-1] 表示使用 s[i-1] 和 t[j-1] 匹配
                    # 2. dp[i-1][j] 表示不使用 s[i-1],但保留 s[0:i-2] 和 t[j-1] 的匹配
                    dp[i][j] = dp[i-1][j-1] + dp[i-1][j]
                else:
                    # 如果 s[i-1] 和 t[j-1] 不相等,则只能不使用 s[i-1] 来匹配
                    dp[i][j] = dp[i-1][j]
        
        # 返回 dp 数组的最后一个元素,表示 s 的子序列中 t 出现的个数
        return dp[-1][-1]

力扣题目链接
题目文章讲解
题目视频讲解

二、力扣583. 两个字符串的删除操作

给定两个单词 word1 和 word2 ,返回使得 word1 和 word2 相同所需的最小步数
每步 可以删除任意一个字符串中的一个字符。
示例
输入: word1 = “sea”, word2 = “eat”
输出: 2
解释: 第一步将 “sea” 变为 “ea” ,第二步将 "eat "变为 “ea”

  1. 确定 dp 数组的定义
    dp[i][j] 表示使 word1[0:i-1] 和 word2[0:j-1] 相同所需删除字符的最小总数。

  2. 状态转移方程
    (1)当 word1[i-1] == word2[j-1]:
    说明当前字符已经匹配,不需要额外的删除操作,直接继承 dp[i-1][j-1] 的结果,即 dp[i][j] = dp[i-1][j-1]。
    (2)当 word1[i-1] != word2[j-1]:
    有三种可能的删除操作:
    I. 删除 word1[i-1]:那么最少操作次数为 dp[i-1][j] + 1。
    II. 删除 word2[j-1]:那么最少操作次数为 dp[i][j-1] + 1。
    III. 同时删除 word1[i-1] 和 word2[j-1]:那么最少操作次数为 dp[i-1][j-1] + 2。
    状态转移方程:dp[i][j] = min(dp[i-1][j] + 1, dp[i][j-1] + 1)。
    这里特别需要注意的是,dp[i-1][j-1] + 2 是在同时删除两个字符的情况下出现的,而删除两个字符的操作等价于连续执行两次删除操作。因此,上述公式可以简化为:
    简化后的公式:dp[i][j] = min(dp[i-1][j] + 1, dp[i][j-1] + 1)。

  3. dp 数组的初始化
    dp[i][0] 初始化:
    当 word2 为空字符串时,word1[0:i-1] 要删除 i 个字符才能与 word2 相同,因此 dp[i][0] = i。
    dp[0][j] 初始化:
    当 word1 为空字符串时,word2[0:j-1] 要删除 j 个字符才能与 word1 相同,因此 dp[0][j] = j。

  4. 确定遍历顺序
    由于 dp[i][j] 依赖于 dp[i-1][j-1]、dp[i-1][j] 和 dp[i][j-1],所以遍历时需要从左到右,从上到下进行。

  5. 最终结果
    dp[len(word1)][len(word2)] 就是使两个字符串相同所需删除字符的最小总数。

代码实现

class Solution:
    def minDistance(self, word1: str, word2: str) -> int:
        # 创建一个二维数组 dp,初始化为 0
        dp = [[0] * (len(word2) + 1) for _ in range(len(word1) + 1)]
        
        # 初始化 dp[i][0],表示 word2 为空时,word1 需要删除的字符数
        for i in range(len(word1) + 1):
            dp[i][0] = i
        
        # 初始化 dp[0][j],表示 word1 为空时,word2 需要删除的字符数
        for j in range(len(word2) + 1):
            dp[0][j] = j
        
        # 遍历 word1 和 word2,填充 dp 数组
        for i in range(1, len(word1) + 1):
            for j in range(1, len(word2) + 1):
                if word1[i-1] == word2[j-1]:
                    # 如果 word1[i-1] 和 word2[j-1] 相等,不需要删除任何字符
                    dp[i][j] = dp[i-1][j-1]
                else:
                    # 如果不相等,取删除操作的最小值
                    dp[i][j] = min(dp[i-1][j-1] + 2, dp[i-1][j] + 1, dp[i][j-1] + 1)
        
        # 返回 dp 数组的最后一个元素,即使两个字符串相同所需删除字符的最小总数
        return dp[-1][-1]

力扣题目链接
题目文章讲解
题目视频讲解

三、力扣72. 编辑距离

给你两个单词 word1 和 word2, 请返回将 word1 转换成 word2 所使用的最少操作数 。
你可以对一个单词进行如下三种操作:
      插入一个字符
      删除一个字符
      替换一个字符
示例
输入:word1 = “horse”, word2 = “ros”
输出:3
解释
horse -> rorse (将 ‘h’ 替换为 ‘r’)
rorse -> rose (删除 ‘r’)
rose -> ros (删除 ‘e’)

  1. 确定 dp 数组的定义
    dp[i][j] 表示将 word1[0:i-1] 转换为 word2[0:j-1] 所使用的最少操作数。

  2. 状态转移方程
    当 word1[i-1] == word2[j-1]:
    如果最后一个字符相同,则不需要做任何操作,dp[i][j] 直接等于 dp[i-1][j-1],即 dp[i][j] = dp[i-1][j-1]。
    当 word1[i-1] != word2[j-1]:
    需要进行编辑操作,分三种情况:
    (1)删除 word1[i-1]:转换操作数等于 dp[i-1][j] + 1,表示将 word1[0:i-2] 转换为 word2[0:j-1] 后删除一个字符。
    (2)删除 word2[j-1] 或 插入一个字符到 word1:转换操作数等于 dp[i][j-1] + 1,表示将 word1[0:i-1] 转换为 word2[0:j-2] 后在 word1 尾部插入一个字符。
    (3)替换 word1[i-1] 为 word2[j-1]:转换操作数等于 dp[i-1][j-1] + 1,表示将 word1[0:i-2] 转换为 word2[0:j-2] 后替换一个字符。
    状态转移方程:dp[i][j] = min(dp[i-1][j-1], dp[i-1][j], dp[i][j-1]) + 1

  3. dp 数组的初始化
    dp[i][0] 初始化:
    将 word1 转换为空字符串 word2 需要删除所有字符,因此 dp[i][0] = i。
    dp[0][j] 初始化:
    将空字符串 word1 转换为 word2 需要插入所有字符,因此 dp[0][j] = j。

  4. 确定遍历顺序
    由于 dp[i][j] 依赖于 dp[i-1][j-1]、dp[i-1][j] 和 dp[i][j-1],所以遍历时需要从左到右,从上到下进行。

  5. 最终结果
    dp[len(word1)][len(word2)] 就是将 word1 转换为 word2 所使用的最少操作数。

代码实现

class Solution:
    def minDistance(self, word1: str, word2: str) -> int:
        # 创建一个二维数组 dp,初始化为 0
        dp = [[0] * (len(word2) + 1) for _ in range(len(word1) + 1)]
        
        # 初始化 dp[i][0],表示将 word1 的前 i 个字符转换为空字符串所需的最少操作数
        for i in range(len(word1) + 1):
            dp[i][0] = i
        
        # 初始化 dp[0][j],表示将空字符串转换为 word2 的前 j 个字符所需的最少操作数
        for j in range(len(word2) + 1):
            dp[0][j] = j
        
        # 遍历 word1 和 word2,填充 dp 数组
        for i in range(1, len(word1) + 1):
            for j in range(1, len(word2) + 1):
                if word1[i-1] == word2[j-1]:
                    # 如果 word1[i-1] 和 word2[j-1] 相等,不需要编辑操作
                    dp[i][j] = dp[i-1][j-1]
                else:
                    # 如果不相等,取增、删、替换操作的最小值加 1
                    dp[i][j] = min(dp[i-1][j-1], dp[i-1][j], dp[i][j-1]) + 1
        
        # 返回 dp 数组的最后一个元素,即将 word1 转换为 word2 所需的最少操作数
        return dp[-1][-1]

力扣题目链接
题目文章讲解
题目视频讲解

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值