本题的第一反应应该是进行最长公共子序列的抽象化,因为删除元素这个操作看上去很复杂。但实际上,也能通过 dp 来记录删除操作,也为后面那道编辑距离打下了基础。
从两个不同的字符串删除元素直到相同,也可以理解为找到最长公共子序列之后就删除多余的元素。这样的抽象化后,可以直接套用找最长公共子序列的解法,在最后用两个字符串的总长度减去两倍的最长公共子序列长度即可。
class Solution:
def minDistance(self, word1: str, word2: str) -> int:
dp = [[0] * len(word2) for _ in range(len(word1))]
first_row_flag = first_col_flag = False
for j in range(len(word2)):
if word1[0] == word2[j]:
first_row_flag = True
if first_row_flag:
dp[0][j] = 1
for i in range(len(word1)):
if word1[i] == word2[0]:
first_col_flag = True
if first_col_flag:
dp[i][0] = 1
for i in range(1, len(word1)):
for j in range(1, len(word2)):
if word1[i] == word2[j]:
dp[i][j] = dp[i-1][j-1] + 1
else:
dp[i][j] = max(dp[i-1][j], dp[i][j-1])
return len(word1) + len(word2) - 2 * dp[-1][-1]
删除元素看着很复杂,因为从结果上来看,删除这个步骤可以发生在任一字符串的任一位置,没办法通过 dp 找到规律。但实际上,只要在 dp 的递推中正确递推了最优解,那就可以解题。
初始化终于变的复杂了,不能再直接初始化第一行和第一列。对于这样的数据,构造对应 "" 的第0行和第0列才是正道!
dp[i][j] 是 word1[:i] 和 word2[:j] 能够达到相同需要的最少删除数(这里定义的改变和后面的)word1[i] == word2[j],那么不需要对这两个尾部元素进行任何删除,dp[i][j] = dp[i-1][j-1]dp[i][j] = min(dp[i-1][j], dp[i][j-1])
dp[i-1][j-1] + 2 被包含在了 dp[i-1][j], dp[i][j-1] 中,可以不需要额外考虑word1="" 来说,需要删除 word2[:j] 中所有的元素才行,也就是 dp[0][j] = jdp[i][0] = iword1 = "sea", word2 = "eat"| ‘’ | s | e | a | |
|---|---|---|---|---|
| ‘’ | 0 | 1 | 2 | 3 |
| e | 1 | 2 | 1 | 2 |
| a | 2 | 3 | 2 | 1 |
| t | 3 | 4 | 3 | 2 |
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][j-1], dp[i-1][j]) + 1
return dp[-1][-1]
dp 数组的下标含义:dp[i][j] 是将 word1[:i] 转换成 word2[:j] 的最少操作数(定义和初始化有点关系,类似于背包问题)
dp 递推公式:
word1[i-1] == word2[j-1],那么各自的结尾元素不需要进行任何操作,直接转换成之前的子问题,dp[i][j] = dp[i-1][j-1]word1[:i] 的后面加上一个 word2[i-1],这样将当前问题转换成了子问题:“将 word1[:i] 转换成 word2[:j-1]”,dp[i][j] = dp[i][j-1] + 1word1[i-1],这样将当前问题转换成了子问题:“将 word1[:i-1] 转换成 word2[:j]”,dp[i][j] = dp[i-1][j] + 1word1[i-1] 变成 word2[j-1],这样将当前问题转换成了子问题:“将 word1[:i-1] 转换成 word2[:j-1]”,dp[i][j] = dp[i-1][j-1] + 1dp[i][j] = min([dp[i-1][j-1], dp[i-1][j], dp[i][j-1]]) + 1dp 数组的初始化:本题如果直接进行第一行和第一列的初始化,难度无疑逆天,所以才用了背包问题一样处理 "" 的定义来帮助初始化
i=0 (word1 = ""),将其转化成 word2[:j] 显然需要 j 次操作(insert),所以 dp[0][j] = jj=0 (word2 = ""),将 word2[:i] 其转化成 "" 显然需要 i 次操作(delete),所以 dp[i][0] = iword1, word2 要正常多了dp 的遍历顺序:递推依赖左上方的元素,从上到下、从左到右即可
举例说明:word1 = "horse", word2 = "ros"
'' | r | o | s | |
|---|---|---|---|---|
'' | 0 | 1 | 2 | 3 |
| h | 1 | 1 | 2 | 3 |
| o | 2 | 2 | 1 | 2 |
| r | 3 | 2 | 2 | 2 |
| s | 4 | 3 | 3 | 2 |
| e | 5 | 4 | 4 | 3 |
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], dp[i-1][j]]) + 1
return dp[-1][-1]
在之前的讲解中,或多或少提到了编辑距离。那些题目或多或少都有一些别的解法,或者看作是重复字数组、重复子序列的变种。但是,实际上他们都能被看作是编辑距离这个终极版本的削弱版本:
t 的元素的情况下能否得到 st 的元素的方法能够得到 s