Leetcode刷题笔记——动态规划之子序列问题篇

Leetcode刷题笔记——动态规划之子序列问题篇

一、回文

第一题:回文子串

Leetcode647. 回文子串:中等题 (详情点击链接见原题)

给你一个字符串 s ,请你统计并返回这个字符串中 回文子串 的数目。
回文字符串 是正着读和倒过来读一样的字符串

1. 确定 dp 数组(dp table)以及下标的含义
在定义 dp 数组的时候 很自然就会想题目求什么,我们就如何定义 dp 数组,但是在本题中,如果我们定义 dp[i] 为下标 i 结尾的字符串有 dp[i] 个回文串的话,我们会发现很难找到递推关系
布尔类型的dp[i][j]:表示区间范围 [i,j] (注意是左闭右闭区间) 的子串是否为回文子串,如果是则为True,否则为 False

2. 确定递推公式
s[i]s[j] 不相等,dp[i][j] 一定是 false
s[i]s[j] 相等时,有如下三种情况
case1:下标 i 与下标 j 相同,同一个字符当然是回文子串
case2:下标 ij 相差为1 ,如aa的时候也是回文子串
case3:下标ij大于 1 的时候,例如cabac,此时s[i] == s[j],判定区间[i,j]是不是回文子串就看[i + 1, j - 1]是不是回文(为True)就可以了
在这里插入图片描述
3. dp 数组如何初始化
dp[i][j] 初始化为 false,因为不可能一开始就全匹配上

4. 确定遍历顺序
这道题的遍历顺序有点讲究,如果按照我们的惯性思维从上到下,从左到右取遍历,那么就得不出结果,从递推公式可以看出,dp[i][j] 的取值取决于 dp[i + 1][j - 1] 是否为 True, 而 dp[i + 1][j - 1]dp[i][j] 的左下角
在这里插入图片描述
所以一定要从下到上,从左到右遍历,这样保证dp[i + 1][j - 1]都是经过计算的

5. 举例推导dp数组
cbabc为例,对应的 dp 数组为
在这里插入图片描述

python代码解法

class Solution:
    def countSubstrings(self, s: str) -> int:
        dp = [[False] * len(s) for _ in range(len(s))]
        result = 0  # result 用来保存回文子串的数目
        for i in range(len(s) - 1, -1, -1):  # 从下到上
            for j in range(i, len(s)):    # 从左到右
                if s[i] == s[j]:
                    if j - i <= 1:
                        result += 1
                        dp[i][j] = True
                    elif dp[i + 1][j - 1]:
                        result += 1
                        dp[i][j] = True
        return result

第二题:最长回文子串

Leetcode5:最长回文子串:中等题 (详情点击链接见原题)

给你一个字符串 s,找到 s 中最长的回文子串。
如果字符串的反序与原始字符串相同,则该字符串称为回文字符串

解题思路
与上题基本一致,这种着重讲不同点,这里返回的结果集是最长的回文子串

确定 dp 数组(dp table)以及下标的含义
布尔类型的dp[i][j]:表示区间范围 [i,j] (注意是左闭右闭区间) 的子串是否为回文子串,如果是则为True,否则为 False

搞清楚 dp 数组的含义,遍历顺序是重点(从下到上,从左到右)!!!
注意:因为 dp[i][j] 的定义,所以 j 一定是大于等于 i 的,那么在填充 dp[i][j] 的时候一定是只填充右上半部分

python代码解法

class Solution:
    def longestPalindrome(self, s: str) -> str:
        dp = [[False] * len(s) for _ in range(len(s))]
        max_len = 0  # result 用来保存最长的回文子串
        result = ""
        for i in range(len(s) - 1, -1, -1):  # 从下到上
            for j in range(i, len(s)):    # 从左到右
                if s[i] == s[j]:
                    if j - i <= 1 or dp[i + 1][j - 1]:
                        dp[i][j] = True
                    elif dp[i + 1][j - 1]:
                    	dp[i][j] = True
                    	
                if dp[i][j] and j - i + 1 > max_len:
                    max_len = j - i + 1
                    result = s[i: i + max_len]
        return result

第三题: 最长回文子序列

Leetcode516. 最长回文子序列:中等题 (详情点击链接见原题)

给你一个字符串 s ,找出其中最长的回文子序列,并返回该序列的长度。
子序列定义为:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列

解题思路
回文子串是要连续的,回文子序列不是连续的

1. 确定 dp 数组(dp table)以及下标的含义
dp[i][j]: 字符串 s[i, j] 范围内最长的回文子序列的长度为 dp[i][j]

2. 确定递推公式
在判断回文子串的题目中,关联逻辑就是看 s[i]s[j] 是否相同,如果 s[i]s[j] 相同, 那么 dp[i][j] = dp[i + 1][j - 1] + 2

如果 s[i]s[j] 不相同,说明 s[i]s[j] 的同时加入并不能增加 [i, j] 区间回文子序列的长度,那么分别加入s[i], s[j] 看看哪一个可以组成最长的回文子序列

加入 s[j] 的回文子序列长度为 dp[i + 1][j]
加入 s[i] 的回文子序列长度为 dp[i][j - 1]
dp[i][j] = max(dp[i + 1][j], dp[i][j - 1])

if s[i] == s[j]:
	dp[i][j] = dp[i + 1][j - 1] += 2
else:
	dp[i][j] = max(dp[i + 1][j], dp[i][j - 1])

3. dp 数组如何初始化
首先考虑 i 和 j 相同的情况,因为递推公式可以看出我们是计算不到 ij 相同的情况的,所以需要手动初始化一下
i==jdp[i][j] = 1, 即一个字符的回文子序列长度就是 1,其他情况 dp[i][j] 初始为 0

4. 确定遍历顺序
可以看出 dp[i][j] 依赖于 dp[i + 1][j]dp[i][j - 1]dp[i + 1][j - 1], 所以遍历 i 的时候一定要从下到上遍历,这样才能保证下一行的数据是经过计算的
在这里插入图片描述

5. 举例推导dp数组

在这里插入图片描述

python代码解法

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

        for i in range(n - 1, -1, -1):
            for j in range(i + 1, n):
                if s[i] == s[j]:
                    dp[i][j] = dp[i + 1][j - 1] + 2
                else:
                    dp[i][j] = max(dp[i + 1][j], dp[i][j - 1])
        return dp[0][n - 1]

第四题: 分割回文串 II

Leetcode132. 分割回文串 II:困难题 (详情点击链接见原题)

给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是
回文串,返回符合要求的 最少分割次数

1. 确定 dp 数组(dp table)以及下标的含义
dp[i]:范围是 [0, i] 的回文子串,最少的分割次数是 dp[i]

2. 确定递推公式
如果要对长度为 [0, i] 的子串进行分割,分割点为 j,如果分割后,区间 [j + 1, i] 是回文子串,那么 dp[i] = dp[j] + 1
注意:这里不是要 dp[j] + 1dp[i] 去比较,而是要在遍历 j 的过程中取最小的 dp[i]

3. dp 数组如何初始化
dp[i]:范围是 [0, i] 的回文子串,最少的分割次数是 dp[i]dp[0] = 0,长度为 1 的字符串最小分割次数就是 0,因为从递推公式中可以看出,dp[i] = min(dp[i], dp[j] + 1) 中我们可以看出每次要取最小的 dp[i],那么非零下标的 dp[i] 就应该初始化为一个最大数

4. 确定遍历顺序
根据递推公式:dp[i] = min(dp[i], dp[j] + 1)j 是在 [0, i] 之间,所以遍历 ifor 循环一定在外层,遍历 jfor 循环在内层才能通过计算过的 dp[j] 的数值推导出 dp[i]

python代码解法

import sys


class Solution:
    def minCut(self, s: str) -> int:
        n = len(s)
        is_palindromic = [[False for _ in range(n)] for _ in range(n)]   # 二维数组is_palindromic[i][j]记录[i,j]是不是回文子串
        for i in range(len(s) - 1, -1, -1):
            for j in range(i, len(s)):
                if s[i] == s[j] and (j - i <= 1 or is_palindromic[i + 1][j - 1]):
                    is_palindromic[i][j] = True
        dp = [sys.maxsize for _ in range(n)]
        dp[0] = 0

        for i in range(1, n):
            if is_palindromic[0][i]:
            	# s[0:i]是回文了,那么dp[i] = 0,一次也不用分割 
                dp[i] = 0
                continue
            for j in range(i):
                if is_palindromic[j + 1][i]:
                    dp[i] = min(dp[i], dp[j] + 1)

        return dp[n - 1]

二、子序列(连续)

第一题:最长重复子数组

Leetcode718. 最长重复子数组:中等题 (详情点击链接见原题)

给两个整数数组 nums1nums2 ,返回 两个数组中 公共的 、长度最长的子数组的长度

解题思路:子数组其实就是连续子序列

1. 确定 dp 数组(dp table)以及下标的含义
dp[i][j]:以下标 i - 1 为结尾的 A 和以下标 j - 1 为结尾的 B,最长重复子数组的长度为 dp[i][j]dp[i][j] 的定义决定了我们在遍历 dp[i][j]的时候 ij 都要从 1 开始】

以 A=[1, 2, 3, 2, 1],B = [3, 2, 1, 4, 7]为例,递推过程如下图所示:
dp[4][2] = 2 的含义为以 下标3为结尾的 A数组 与下标 1 为结尾的 B 数组 的最长重复子数组的长度为 2
在这里插入图片描述

2. 确定递推公式
dp[i][j] 的状态只能由 dp[i - 1][j - 1] 推导出来,即当 A[i - 1]B[i - 1] 相等的时候,dp[i][j] = dp[i - 1][j - 1] + 1

3. dp数组如何初始化
根据 dp[i][j] 的定义,dp[i][0]dp[0][j] 其实都是没有意义的【可以看成是以 i - 1 为结尾的 A 和空数组 B 的的最长重复子数组】, 故 dp[i][0]dp[0][j] 初始化为 0

4. 确定遍历顺序
外层 for 循环遍历 A, 内层 for 循环遍历 B

python代码解法

class Solution:
    def findLength(self, nums1: List[int], nums2: List[int]) -> int:
        n, m = len(nums1), len(nums2)
        result = 0
        dp = [[0 for _ in range(m + 1)] for _ in range(n + 1)]
        for i in range(1, n + 1):  # 外层循环遍历 nums1
            for j in range(1, m + 1):  # 内层循环遍历 nums2
                if nums1[i - 1] == nums2[j - 1]:
                    dp[i][j] = dp[i - 1][j - 1] + 1
                result = max(result, dp[i][j])
        # for i in dp:    # 打印 dp 数组
        #     print(i)
        return result

第二题:最长连续递增序列

Leetcode674. 最长连续递增序列:简单题 (详情点击链接见原题)

给定一个未经排序的整数数组,找到最长且 连续递增的子序列,并返回该序列的长度

解题思路:
1. 确定 dp 数组(dp table)以及下标的含义
dp[i]: 以下标 i 为结尾的连续递增的子序列长度为 dp[i](注意这里说以下标 i 为结尾,并没说一定以下标 0 为起始位置)

2. 确定递推公式
如果 nums[i] > nums[i -1],那么以 i 为结尾的连续递增的子序列长度一定等于 以 i - 1 为结尾的连续递增的子序列长度 + 1
递推公式: dp[i] = dp[i - 1] + 1
本题要求的是连续递增子序列,所以只需要比较 nums[i]nums[i - 1],而不用去比较 nums[j]nums[i]j0i 之间遍历】

3. dp数组如何初始化
以下标 i 为结尾的连续递增的子序列长度最少也应该是 1,即 nums[i] 这一个元素

4. 确定遍历顺序
从递推公式上可以看出,dp[i ] 依赖 dp[i - 1],所以一定是从前向后遍历

python代码解法(dp思路)

class Solution:
    def findLengthOfLCIS(self, nums: List[int]) -> int:
        dp = [1] * len(nums)
        for i in range(1, len(nums)):
            if nums[i] > nums[i - 1]:
                dp[i] = dp[i - 1] + 1
        return max(dp)

python代码解法(滑窗思路)

class Solution:
    def findLengthOfLCIS(self, nums: List[int]) -> int:
        left, right = 0, 1
        ans = 1
        while right < len(nums):
            if nums[right] <= nums[right - 1]:
                left = right
            ans = max(ans, right - left + 1)
            right += 1
        return ans

第三题:最大子数组和

Leetcode53. 最大子数组和:中等题 (详情点击链接见原题)

给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和

1. 确定 dp 数组(dp table)以及下标的含义
dp[i]:包括下标 i (以 nums[i] 为结尾)的最大连续子序列和为 dp[i]

2. 确定递推公式
dp[i] 只有两个方向可以推导出来:因为 dp[i - 1] < 0 的话会拉低连续子序列的和,如果拉低还不如直接从当前 nums[i] 开始算)
dp[i - 1] + nums[i],即加入 nums[i] 后的连续子序列和(
nums[i]:从头开始计算当前连续子序列和

3. dp数组如何初始化
dp[0] = nums[0]

4. 确定遍历顺序

递推公式中 dp[i] 依赖于 dp[i - 1] 的状态,需要从前向后遍历

注:我们要找最大的连续子序列,就应该找每一个 i 为终点的连续最大子序列

python代码解法

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        dp = [0] * len(nums)
        dp[0] = nums[0]
        for i in range(1, len(nums)):
            dp[i] = max(dp[i - 1] + nums[i], nums[i])
        # print(dp)
        return max(dp)

三、子序列(不连续)

第一题: 最长递增子序列

Leetcode300. 最长递增子序列:中等题 (详情点击链接见原题)

给你一个整数数组 nums ,找到其中最长严格递增子序列的长度
子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序

解题思路:
相对于 Leetcode674. 最长连续递增序列 这一题,本题最大的区别在于不连续

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

dp[i]:表示 i 之前包括 i 的以 nums[i] 结尾的最长递增子序列的长度

2. 确定递推公式
位置 i 的最长升序子序列等于 j0i - 1各个位置的最长升序子序列 + 1 的最大值

if nums[i] > nums[j]:
	dp[i] = max(dp[i], dp[j + 1])  # 注意这里不是要 dp[i] 与 dp[j] + 1 进行比较, 而是取dp[j] + 1的最大值

3. dp 数组如何初始化
每一个i,对应的 dp[i](即最长递增子序列)起始大小至少都是 1

4. 确定遍历顺序
dp[i] 是有0i - 1各个位置的最长递增子序列 推导而来,那么遍历 i 一定是从前向后遍历
j 其实就是遍历 0i - 1, 那么是从前到后还是从后到前都可以

5. 举例推导dp数组

以数组 [0,1,0,3,2] 为例
在这里插入图片描述

python代码解法

class Solution:
    def lengthOfLIS(self, nums: List[int]) -> int:
        dp = [1] * len(nums)
        for i in range(1, len(nums)):
            for j in range(0, i):
                if nums[i] > nums[j]:
                    dp[i] = max(dp[i], dp[j] + 1)
        # print(dp)
        return max(dp)

第二题:最长公共子序列

Leetcode1143:最长公共子序列:中等题 (详情点击链接见原题)

给定两个字符串 text1text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0

1. 确定 dp 数组(dp table)以及下标的含义
dp[i][j]:长度为 [0, i - 1] 的字符串 text1 与长度为 [0, j - 1] 的字符串 text2 的最长公共子序列为 dp[i][j]

2. 确定递推公式
case1: 如果 text1[i - 1]text2[j - 1] 相同,那么说明找到了一个公共元素,所以 dp[i][j] = dp[i - 1][j - 1] + 1
case2: 如果 text1[i - 1]text2[j - 1] 不相同 ,那么就看看 text1[0, i - 2]text2[0, j - 1] 的最长公共子序列和 text1[0, i - 1]text2[0, j-2] 的最长公共子序列,取最大的
所以递推公式为:dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])

3. dp 数组如何初始化
dp[i][0]text1[0, i - 1] 和空串的最长公共子序列是 0dp[i][0] = 0,同理 dp[0][j] = 0
这也是为什么将 dp[i][j] 定义为长度为 [0, i - 1] 的字符串和 []0, j - 1 的字符串,主要是方便初始化

4. 确定遍历顺序
从前向后,从上到下来遍历
在这里插入图片描述

5. 举例推导dp数组
以输入 text1 = ”abcde“,text2=”ace“ 为例

在这里插入图片描述

python代码解法

class Solution:
    def longestCommonSubsequence(self, text1: str, text2: str) -> int:
        n, m = len(text1), len(text2)
        dp = [[0 for _ in range(m + 1)] for _ in range(n + 1)]
        for i in range(1, n + 1):
            for j in range(1, m + 1):
                if text1[i - 1] == text2[j - 1]:
                    dp[i][j] = dp[i - 1][j - 1] + 1
                else:
                    # text1[0, i - 2] 与 text2[0, j - 1]的最长公共子序列
                    # text1[0, i - 1] 与 text2[0, j - 2]的最长公共子序列
                    dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
        return dp[n][m]

第三题:不相交的线

Leetcode1035. 不相交的线:中等题 (详情点击链接见原题)

在两条独立的水平线上按给定的顺序写下 nums1nums2 中的整数

四、编辑距离

第一题:判断子序列

Leetcode392. 判断子序列:简单题 (详情点击链接见原题)

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

解题思路
1. 确定 dp 数组(dp table)以及下标的含义
dp[i][j]:长度为 [0, i - 1] 的字符串 s 与长度为 [0, j - 1] 的字符串 t 的相同子序列的长度为 dp[i][j]
注:判断 s 是否为 t 的子序列。即 t 的长度是大于等于 s

2. 确定递推公式

if s[i - 1] == t[i - 1]:    # t中找到一个字符在s中也出现了
	dp[i][j] = dp[i - 1][j - 1] + 1
if s[i - 1] != t[i - 1]:    # 相当于 t 要删除元素,继续匹配
	dp[i][j] = dp[i][j - 1]

3. dp 数组如何初始化
dp[i][j] 是依赖于 dp[i - 1][j - 1] 的,所以 dp[0][0]dp[i][0] 是一定要初始化的
在这里插入图片描述
4. 确定遍历顺序
dp[i][j] 都是依赖于 dp[i - 1][j - 1]dp[i][j - 1],所以应该从前向后,从上到下来遍历

5. 举例推导dp数组
在这里插入图片描述
由于 dp[i][j] 表示以下标 i - 1 为结尾的字符串 s 和以下标 j - 1 为结尾的字符串 t 相同子序列的长度,所以如果 dp[len(s)][len(t)] 与字符串 s 的长度相同说明:st 的最长相同子序列就是 s,那么 s 就是 t 的子序列

python代码解法

class Solution:
    def isSubsequence(self, s: str, t: str) -> bool:
        dp = [[0 for _ in range(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]
        return True if dp[len(s)][len(t)] == len(s) else False

第二题: 不同的子序列

Leetcode115. 不同的子序列:困难题 (详情点击链接见原题)

给你两个字符串 st ,统计并返回在 s 的 子序列 中 t 出现的个数,结果需要对 10的9次方 + 7 取模

解题思路:
本题相对于编辑距离还是比较简单的,因为本题只有删除操作,本题求的是 s 里面有多少个像 t 这样的子序列,其实就是问这个 s 字符串中有多少种删除元素的方式使得 s 可以变成 t

1. 确定 dp 数组(dp table)以及下标的含义
dp[i][j]:i - 1 为结尾的 s 子序列种出现以 j - 1 为结尾的 t个数dp[i][j]

2. 确定递推公式
case1: s[i - 1] 与 t[j - 1] 相等
s:bagg t: bag
s[3]t[2] 是相同的,但是字符串 s 也可以不用 s[3] 来匹配,s[0]s[1]s[2]s[0]s[1]s[3] 组成的 bag
dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j]

不需要考虑 st 的最后一位字母,只需要用 dp[i - 1][j - 1]
case2: s[i - 1]t[j - 1] 不相等
s[i - 1]t[j - 1]不相等时,dp[i][j] 只有一部分组成,不用 s[i - 1] 来匹配

3. dp 数组如何初始化
由递推公式可知: dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j]; 和 dp[i][j] = dp[i - 1][j],dp[i][j] 是从上方和左上方推导而来的,所以 dp[i][0]dp[0][j] 是一定要初始化的
dp[i][0]: 以下标 i - 1为结尾的 s 删除所有元素,出现空串t【即一种删除所有元素的方式】
dp[0][j]: 空串 s 无论怎么都变成不了 t,所以 dp[0][j] = 0
dp[0][0] = 1: 空字符串 s 可以删除 0 个元素变成空字符串 t

4. 确定遍历顺序

5. 举例推导dp数组

python代码解法

class Solution:
    def numDistinct(self, s: str, t: str) -> int:
        dp = [[0 for _ in range(len(t) + 1)] for _ in range(len(s) + 1)]
        for i in range(len(s)):
            dp[i][0] = 1
        for j in range(1, len(t)):
            dp[0][j] = 0
        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)]

第三题:两个字符串的删除操作

Leetcode583. 两个字符串的删除操作:中等题 (详情点击链接见原题)

给定两个单词 word1word2 ,返回使得 word1word2 相同所需的最小步数

解题思路
相对于上一题而言,其实就是两个字符串都可以删了
1. 确定 dp 数组(dp table)以及下标的含义
dp[i][j]: 以 i-1 为结尾的字符串 word1,和以 j-1 位结尾的字符串 word2,想要达到相等,所需要删除元素的最少次数

2. 确定递推公式

  • word1[i - 1]word2[j - 1] 相同的时候【不用删除元素】dp[i][j] = dp[i - 1][j - 1]
  • word1[i - 1]word2[j - 1]不相同的时候:dp=min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + 2)
    • case1:删 word1[i - 1],最少操作次数为 dp[i - 1][j] + 1
    • case2: 删 word2[j - 1],最少操作次数为 dp[i][j - 1] + 1
    • case3:同时删 word1[i - 1]word2[j - 1],最少操作次数为 dp[i - 1][j - 1] + 2

3. dp 数组如何初始化
从递推公式中,可以看出来,dp[i][0]dp[0][j] 是一定要初始化的
dp[i][0]word2 为空字符串,以 i-1 为结尾的字符串 word1 要删除多少个元素,才能和 word2 相同呢,很明显 dp[i][0] = i, dp[0][j] = j

4. 确定遍历顺序
从递推公式 dp[i][j] = min(dp[i - 1][j - 1] + 2, min(dp[i - 1][j], dp[i][j - 1]) + 1); 和 dp[i][j] = dp[i - 1][j - 1] 可以看出 dp[i][j] 都是根据左上方、正上方、正左方推出来的

5.举例推导DP数组
word1:sea,word2:eat 为例
在这里插入图片描述

python代码解法

class Solution:
    def minDistance(self, word1: str, word2: str) -> int:
        dp = [[0 for _ in range(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)
        return dp[len(word1)][len(word2)]

第四题:编辑距离

Leetcode72. 编辑距离:中等题 (详情点击链接见原题)

给你两个单词 word1word2, 请返回将 word1 转换成 word2 所使用的最少操作数`

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

dp[i][j]:表示以下标 i - 1 为结尾的字符串 word1 和以下标 j - 1 为结尾的字符串 word2,最近的编辑距离为 dp[i][j]

2. 确定递推公式
if word1[i - 1] != word2[j - 1]
操作1: word1 删除一个元素,那么就是以下标 i - 2 为结尾的 word1j-1 为结尾的 word2 的最近编辑距离 再加上一个操作dp[i][j] = dp[i - 1][j] + 1
操作2word2 删除一个元素,那么就是以下标 i - 1 为结尾的 word1j-2 为结尾的 word2 的最近编辑距离 再加上一个操作,dp[i][j] = dp[i][j - 1] + 1

添加元素怎么操作呢?word2 添加一个元素,相当于 word1 删除一个元素
比如 word1 = 'ad', word2 = 'a'word1 删除元素 dword1 = 'a', word2 = 'a'【操作一次】
word2 添加一个元素 dword1 = 'ad', word2 = 'ad'【操作一次】

操作3:替换元素
word1 替换 word1[i - 1] 使其与 word2[j - 1] 相同,此时不用增删元素,只需一次替换操作就可以让 word1[i - 1]word2[j - 1], dp[i][j] = dp[i - 1][j - 1] + 1

if word1[i - 1] == word2[j - 1]:  # 既然两个元素相同
	dp[i][j] = dp[i - 1][j - 1]   # 考虑以 i - 2 为下标结尾的 word1 和以 j - 2为下标结尾的 word2 的最近的编辑距离
if word1[i - 1] != word2[j - 1]:  # 有增,删,和替换三种操作]
	dp[i][j] = min({dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]}) + 1

3. dp 数组如何初始化
dp[i][0] :以下标 i-1 为结尾的字符串 word1,和空字符串 word2,最近编辑距离为 dp[i][0]dp[i][0] = i 即对 word1 里面的元素全部都做删除操作

4. 确定遍历顺序

  • dp[i][j] = dp[i - 1][j - 1]
  • dp[i][j] = dp[i - 1][j - 1] + 1
  • dp[i][j] = dp[i][j - 1] + 1
  • dp[i][j] = dp[i - 1][j] + 1
    可以看出 dp[i][j] 是依赖左方,上方和左上方元素的,所以 dp 矩阵中一定是从左到右从上到下去遍历

python代码解法

class Solution:
    def minDistance(self, word1: str, word2: str) -> int:
        dp = [[0 for _ in range(len(word2) + 1)] for _ in range(len(word1) + 1)]
        for i in range(0, len(word1) + 1):
            dp[i][0] = i
        for j in range(0, 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 - 1][j], dp[i][j - 1]) + 1
        return dp[len(word1)][len(word2)]
  • 24
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

code_lover_forever

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值