算法学习笔记——动态规划:最小编辑距离

最小编辑距离

LeetCode 72. 编辑距离
希望把字符串 s1 转化为 s2,一次操作可以选择插入/删除/替换,求完成转换的最少操作次数
s1 = “horse”, s2 = “rose”,返回2(horse -> rorse -> rose)

思路:
我们固定s2,然后解决如何从s1转化为s2的问题(两者反过来其实也一样)

套路:两个字符串的动态规划,一般都需要二维dp数组,i、j分别与两个字符串挂钩

  • 如果是“连续”的子串问题,i与 以s1[i]结尾的子串 挂钩
  • 若果是“不连续”的子序列问题,i与 s1[0…i]的子序列 挂钩
  1. 使用dp[i][j]表示子串s1[0...i]到子串s2[0...j]的最小编辑距离(最少操作次数)
    每次只考虑末尾的s1[i]字符怎么向s2[j]字符靠拢
  2. 状态:算法推进中会改变的变量,这里就是指针i、j的位置
    选择:对于当前的两个子串,可以在s1的末尾选择插入/替换/删除,也可以选择什么也不做(两个字串的末尾字符相同,则无需操作,直接将i、j指针同时向前移即可)
  3. 状态转移:如果i、j所指向的字符相同,那么不用操作,i、j同时前移,求更短子串的最小编辑距离;
    如果i、j所指向的字符不同,那么就要在插入/替换/删除中选择一种最优的操作
    插入:所需操作数为dp[i][j-1]+1(在s1末尾插入s2[j]j指针前移)
    删除:所需操作数为dp[i-1][j]+1(在s1末尾删除s1[i]i指针前移)
    替换:所需操作数为dp[i-1][j-1]+1(将s1[i]换为s2[j]i、j指针前移)

实现:
注意,实际应该在dp数组左侧和上侧多插入一个空白行,使用dp[i][j]表示子串s1[0...i-1]到子串s2[0...j-1]的最小编辑距离

  • 这样一方面是防止越界
  • 另一方面是表示了base case:例如,dp[0][j]表示从空字符串''s2[0...j-1]的最小编辑距离(显然这个值就是s2[0...j-1]的长度,即要插入的字符个数)
class Solution:
    def minDistance(self, word1: str, word2: str) -> int:
        len1, len2 = len(word1), len(word2)
        dp = [[0 for _ in range(len2 + 1)] for _ in range(len1 + 1)]
        # base case
        for i in range(len2 + 1):
            dp[0][i] = i
        for j in range(len1 + 1):
            dp[j][0] = j
        for i in range(1, len1 + 1):
            for j in range(1, len2 + 1):
                # dp[i][j]表示子串s1[0...i-1]到子串s2[0...j-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] + 1)  # 替换
        return dp[len1][len2]

你可能感兴趣的:(算法学习笔记,算法,动态规划,学习)