LeetCode-72. 编辑距离
操作单词的方法
我们可以对任意一个单词进行三种操作:
- 插入一个字符;
- 删除一个字符;
- 替换一个字符。
给定两个单词,设为 A 和 B,这样我们就能够进行六种操作方法。然而,我们可以发现以下等价关系:
-
对单词 A 删除一个字符和对单词 B 插入一个字符是等价的。
例如,当单词 A 为
doge,单词 B 为dog时,我们既可以删除单词 A 的最后一个字符e,得到相同的dog,也可以在单词 B 的末尾添加一个字符e,得到相同的doge。 -
对单词 B 删除一个字符和对单词 A 插入一个字符也是等价的。
-
对单词 A 替换一个字符和对单词 B 替换一个字符是等价的。
例如,当单词 A 为
bat,单词 B 为cat时,我们修改单词 A 的第一个字母b -> c,和修改单词 B 的第一个字母c -> b是等价的。
这样以来,本质不同的操作实际上只有三种:
- 在单词 A 中插入一个字符;
- 在单词 B 中插入一个字符;
- 修改单词 A 的一个字符。
class Solution:
def minDistance(self, word1: str, word2: str) -> int:
n, m = len(word1), len(word2)
@cache
def dfs(i: int, j: int) -> int:
if i < 0:
return j + 1
if j < 0:
return i + 1
if word1[i] == word2[j]:
return dfs(i - 1, j - 1)
return min(dfs(i - 1, j), dfs(i, j - 1), dfs(i - 1, j - 1)) + 1
return dfs(n - 1, m - 1)
class Solution:
def minDistance(self, word1: str, word2: str) -> int:
n, m = len(word1), len(word2)
f = [[0] * (m + 1) for _ in range(n + 1)]
f[0] = list(range(m + 1))
for i, x in enumerate(word1):
f[i + 1][0] = i + 1
for j, y in enumerate(word2):
if x == y:
f[i + 1][j + 1] = f[i][j]
else:
f[i + 1][j + 1] = min(f[i][j + 1], f[i + 1][j], f[i][j]) + 1
return f[n][m]
LeetCode-300. 最长递增子序列
dfs(i) 表示以 nums[i] 结尾的最长递增子序列的长度。
class Solution:
def lengthOfLIS(self, nums: List[int]) -> int:
@cache
def dfs(i: int) -> int:
res = 0
for j in range(i):
if nums[j] < nums[i]:
res = max(res, dfs(j))
return res + 1
return max(dfs(i) for i in range(len(nums)))
同记忆化搜索,f[i] 表示以 nums[i] 结尾的最长递增子序列的长度。
class Solution:
def lengthOfLIS(self, nums: List[int]) -> int:
f = [0] * len(nums)
for i, x in enumerate(nums):
for j, y in enumerate(nums[:i]):
if x > y:
f[i] = max(f[i], f[j])
f[i] += 1
return max(f)
贪心 + 二分查找
class Solution:
def lengthOfLIS(self, nums: List[int]) -> int:
tails = []
for num in nums:
idx = bisect_left(tails, num)
if idx == len(tails):
tails.append(num)
else:
tails[idx] = num
return len(tails)
LeetCode-1143. 最长公共子序列
假设字符串 text1 和 text2 的长度分别为 m 和 n,创建一个大小为 (m+1) × (n+1) 的二维数组 dp,其中 dp[i][j] 表示 text1[0:i] 和 text2[0:j] 的最长公共子序列的长度。
说明:
text1[0:i]表示text1的前i个字符。text2[0:j]表示text2的前j个字符。
最长公共子序列(LCS)的动态规划解法
-
假设字符串
text1和text2的长度分别为m和n,创建m+1行n+1列的二维数组dp,其中dp[i][j]表示text1[0:i]和text2[0:j]的最长公共子序列的长度。 -
上述表示中,
text1[0:i]表示text1的前i个字符,text2[0:j]表示text2的前j个字符。
状态转移方程
当 i > 0 且 j > 0 时,考虑 dp[i][j] 的计算:
1. 字符相等的情况
-
条件:
text1[i-1] = text2[j-1] -
解释:
- 这两个相同的字符称为公共字符。
- 可以将这两个字符加入到之前的最长公共子序列中。
-
状态转移:
dp[i][j]=dp[i−1][j−1]+1 dp[i][j] = dp[i-1][j-1] + 1 dp[i][j]=dp[i−1][j−1]+1
2. 字符不相等的情况
- 条件:
text1[i-1] != text2[j-1] - 考虑
text1[0:i-1]和text2[0:j]的最长公共子序列。 - 考虑
text1[0:i]和text2[0:j-1]的最长公共子序列。
dp[i][j]=max(dp[i−1][j],dp[i][j−1]) dp[i][j] = \max(dp[i-1][j], dp[i][j-1]) dp[i][j]=max(dp[i−1][j],dp[i][j−1])
综上,状态转移方程可以表示为:
dp[i][j]={dp[i−1][j−1]+1,如果 text1[i−1]=text2[j−1]max(dp[i−1][j],dp[i][j−1]),如果 text1[i−1]≠text2[j−1] dp[i][j] = \begin{cases} dp[i-1][j-1] + 1, & \text{如果 } text1[i-1] = text2[j-1] \\ \max(dp[i-1][j], dp[i][j-1]), & \text{如果 } text1[i-1] \ne text2[j-1] \end{cases} dp[i][j]={dp[i−1][j−1]+1,max(dp[i−1][j],dp[i][j−1]),如果 text1[i−1]=text2[j−1]如果 text1[i−1]=text2[j−1]
class Solution:
def longestCommonSubsequence(self, text1: str, text2: str) -> int:
@cache
def dfs(i: int, j: int) -> int:
if i < 0 or j < 0:
return 0
if text1[i] == text2[j]:
return dfs(i - 1, j - 1) + 1
return max(dfs(i - 1, j), dfs(i, j - 1))
return dfs(len(text1) - 1, len(text2) - 1)
class Solution:
def longestCommonSubsequence(self, text1: str, text2: str) -> int:
n, m = len(text1), len(text2)
f = [[0] * (m + 1) for _ in range(n + 1)]
for i, x in enumerate(text1):
for j, y in enumerate(text2):
if x == y:
f[i + 1][j + 1] = f[i][j] + 1
else:
f[i + 1][j + 1] = max(f[i][j + 1], f[i + 1][j])
return f[n][m]
LeetCode-3177. 求出最长好子序列 II
- 我们可以想到用
dp[i][j]来表示以nums[i]结尾,其中有j个数字与其在序列中的后一个数字不相等的最长合法序列的长度。其中i的取值小于n(n表示nums的长度),j不超过k。初始时,有:dp[i][0] = 1
-
nums[i] != nums[x]:对于此情况,可以维护一个长度为
k的辅助数组zd。其中zd[j]表示枚举到位置i之前,有j个数字与其在序列中的后一个不相等的最长合法序列的长度,那么可以直接写出转移:dp[i][j]=zd[j−1]+1 dp[i][j] = zd[j-1] + 1 dp[i][j]=zd[j−1]+1
-
nums[i] = nums[x]:假设有下标
a < b < c,并且nums[a] = nums[b] = nums[c],对于c来说如果选取由a转移过来计算答案,那么一定不如a -> b -> c更优,所以会选取下标最近的相同的数进行转移。针对这种情况,dp使用哈希表维护能节省一些空间,并且在哈希表中用nums[i]替换i。在每一次遍历i计算完后更新zd,最后的zd[k]就是答案。
class Solution:
def maximumLength(self, nums: List[int], k: int) -> int:
fs = {}
mx = [0] * (k + 2)
for x in nums:
if x not in fs:
fs[x] = [0] * (k + 1)
f = fs[x]
for j in range(k, -1, -1):
f[j] = max(f[j], mx[j]) + 1
mx[j + 1] = max(mx[j + 1], f[j])
return mx[-1]

被折叠的 条评论
为什么被折叠?



