day43 代码随想录 | 动态规划 大BOSS 子序列到编辑距离

392.判断子序列

给定字符串 s 和 t ,判断 s 是否为 t 的子序列。

字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace"是"abcde"的一个子序列,而"aec"不是)。

示例 1:

  • 输入:s = "abc", t = "ahbgdc"
  • 输出:true

这个题是一个简单题,可以用双指针,也可以动态规划,也是入门编辑距离的入门题,只涉及删除操作。

双指针

一个指针指向s 一个指针指向t   t指针一个移动,s指针如果遇到相等就移动

如果s指针能移动到末尾,说明就可以了

def isSubsequence(self, s: str, t: str) -> bool:
    if len(s) == 0:
        return True

    point1 = 0

    for point2 in range(len(t)):
        if s[point1] == t[point2]:
            point1 += 1
        if point1 >= len(s):
            return True
    return False

动态规划

1. dp数组定义

使用二维dp数组

dp[i][j] 代表 0-i-1 的s 在0-j-1的子序列出现的最大长度

2. dp递推公式

如果相等

dp[i][j] = dp[i-1][j-1] + 1

如果不等

dp[i][j] = dp[i][j-1] # 删除字符t

3. dp数组初始化

dp[i][0] = 0

dp[0]][0]

4. 遍历顺序

从递推公式可以看出,递推的方式是从左到右 从上倒下的

5.

def isSubsequence(self, s: str, t: str) -> bool:
    dp = [[0] * (len(t) + 1) for _ in range(len(s) + 1)]

    for i in range(1, len(s) + 1):
        for j in range(1, len(t) + 1):
            if s[i-1] == t[j-1]:
                dp[i][j] = dp[i-1][j-1] + 1
            else:
                dp[i][j] = dp[i][j-1]

    
    if dp[-1][-1] == len(s):
        return True

    return False

115.不同的子序列

给定一个字符串 s 和一个字符串 t ,计算在 s 的子序列中 t 出现的个数。

字符串的一个 子序列 是指,通过删除一些(也可以不删除)字符且不干扰剩余字符相对位置所组成的新字符串。(例如,"ACE" 是 "ABCDE" 的一个子序列,而 "AEC" 不是)

题目数据保证答案符合 32 位带符号整数范围。

这个题跟上面的题不同的时,你需要统计了个数了,同样也是子序列匹配的问题

数组定义是一样的,但是删除的话,我们也是指对s进行删除

核心变化在递推公式

如果相等,也就是说我们可以直接加上前面i-2和j-2的字符中匹配的,就用s[i-1]来进行匹配

,只需要dp[i-1][j-1]。但我们也可以不用s[i-1]来匹配,个数为dp[i-1][j]

dp[i][j] = dp[i-1][j-1] + dp[i-1][j]

如果不相等

dp[i][j] = dp[i-1][j] 不用s[i - 1]来匹配(就是模拟在s中删除这个元素),即:dp[i - 1][j]

之后是dp数组的初始化

dp[i][0] = 1 为什么? 以i-1为结尾的s可以随便删除元素,出现空字符串的个数。

那么dp[0][j]一定都是0,s如论如何也变成不了t。

dp[0][0]应该是1,空字符串s,可以删除0个元素,变成空字符串t。

class Solution:
    def numDistinct(self, s: str, t: str) -> int:
        dp = [[0] * (len(t) + 1) for _ in range(len(s) + 1)]
        for i in range(len(s) + 1):
            dp[i][0] = 1
        dp[0][0] = 1

        for i in range(1, len(s) + 1):
            for j in range(1, len(t) + 1):
                if s[i-1] == t[j-1]:
                    dp[i][j] = dp[i-1][j-1] + dp[i-1][j]
                else:
                    dp[i][j] = dp[i-1][j]
        
        return dp[len(s)][len(t)]

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

给定两个单词 word1 和 word2,找到使得 word1 和 word2 相同所需的最小步数,每步可以删除任意一个字符串中的一个字符。

示例:

  • 输入: "sea", "eat"
  • 输出: 2
  • 解释: 第一步将"sea"变为"ea",第二步将"eat"变为"ea"

这个题又是类似匹配,但是只有删除操作

  1. 确定dp数组(dp table)以及下标的含义

dp[i][j]:以i-1为结尾的字符串word1,和以j-1位结尾的字符串word2,想要达到相等,所需要删除元素的最少次数。

2. dp递推公式

如果遇到字符相等

那么 dp[i][j] = dp[i-1][j-1] 不需要进行操作

如果字符不相等

情况一:删word1[i - 1],最少操作次数为dp[i - 1][j] + 1

情况二:删word2[j - 1],最少操作次数为dp[i][j - 1] + 1

情况三:同时删word1[i - 1]和word2[j - 1],操作的最少次数为dp[i - 1][j - 1] + 2

要么删除i 要么删除j 要么删除i和j

dp[i][j] = min(dp[i-1][j-1] + 2, dp[i-1][j] + 1, dp[i][j-1] + 1)

3. dp数组初始化

dp[0][j] = j 删除j次

dp[i][0] = i 删除i次

dp[0][0] = 0

class Solution:
    def minDistance(self, word1: str, word2: str) -> int:
        dp = [[0] * (len(word2) + 1) for _ in range(len(word1) + 1)]
        for i in range(len(word1) + 1):
            dp[i][0] = i
        for j in range(len(word2) + 1):
            dp[0][j] = j

        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] + 2)
            
        return dp[-1][-1]

72. 编辑距离

给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数 。

你可以对一个单词进行如下三种操作:

  • 插入一个字符

  • 删除一个字符

  • 替换一个字符

  • 示例 1:

  • 输入:word1 = "horse", word2 = "ros"

  • 输出:3

  • 解释: horse -> rorse (将 'h' 替换为 'r') rorse -> rose (删除 'r') rose -> ros (删除 'e')

  • 示例 2:

  • 输入:word1 = "intention", word2 = "execution"

  • 输出:5

  • 解释: intention -> inention (删除 't') inention -> enention (将 'i' 替换为 'e') enention -> exention (将 'n' 替换为 'x') exention -> exection (将 'n' 替换为 'c') exection -> execution (插入 'u')

和删除操作很像,但是添加、删除和替换的理解挺磨人的

1. dp数组定义

dp[i][j] 是指从0-i-1 的word1字符转换为0-j-1word字符使用的最少操作次数为dp[i][j]

2. dp递推公式

如果相等,那就不用编辑

dp[i][j] = dp[i-1][j-1]

如果不相等

1. 删除操作:删除i 或者删除j dp[i-1][j] + 1  dp[i][j-1] + 1

2. 添加操作,其实就是删除 你删除i可以理解为添加j

3. 替换操作:word1替换word1[i - 1],使其与word2[j - 1]相同,此时不用增删加元素。

不等嘛,我们把不等的替换成等的, 如果相等,此时dp[i][j]等于dp[i-1][j-1],但是我们替换了,因此还要+1.dp[i][j] = dp[i-1][j-1] + 1

这三种操作,我们要选最小的

3. dp数组初始化

dp[0][j] = j 删除j次

dp[i][0] = i 删除i次

结束

class Solution:
    def minDistance(self, word1: str, word2: str) -> int:
        dp = [[0] * (len(word2) + 1) for _ in range(len(word1) + 1)]

        for i in range(len(word1) + 1):
            dp[i][0] = i
        for j in range(len(word2) + 1):
            dp[0][j] = j


        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], dp[i][j-1], dp[i-1][j-1]) + 1
        
        return dp[len(word1)][len(word2)]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值