多味的LeetCode --- 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')

解题思路:

什么是动态规划?通俗地理解来说,一个问题的解决办法一看就知道(穷举),但不能一个一个数啊,你得找到最优的解决办法,换句话说题目中就会出现类似“最多”、“最少”,“一共有多少种”等提法,这些题理论上都能使用动态规划的思想来求解。动态规划与分治方法类似,都是通过组合子问题的解来求解原问题,但它对每个子问题只求解一次,将其保存在表格中,无需重新计算,通常用于求解最优化问题——《算法导论》。

编辑距离(Edit Distance),在本文指的是Levenshtein距离,也就是字符串S1通过插入、修改、删除三种操作最少能变换成字符串S2的次数。例如:S1 = abc,S2 = abf, 编辑距离d = 1(只需将c修改为f)。在本文中将利用动态规划的算法思想对字符串的编辑距离求解。

定义:S1、S2表示两个字符串,S1(i) 表示 S1 的第 i i i 个字符,d[i, j]表示S1的第 i i i 个前缀到 S2的第j个前缀(例如:S1 = ”abc”,S2 = ”def”,求解S1到S2的编辑距离为d[3, 3])。

  1. S1 = ”abc”, S2 = ”dec”,此时它们的编辑距离为 d[3, 3] = 2,观察两个字符串的最后一个字符是相同的,也就是说 S1(3) = S2(3) 不需要做任何变换,故S1 = ”abc”, S2 = ”dec” <= > S1’ = ”ab”, S2’ = ”de”,即当S1[i] = S[j]时,d[i, j] = d[i-1,j -1]。得到公式:d[i, j] = d[i - 1, j - 1] (S1[i] = S2[j])

  2. 上面一条得出了当S1[i] = S2[j]的计算公式,显然还有另一种情况就是S1[i] ≠ S2[j],若S1 = ”abc”, S2 = ”def”。S1变换到S2的过程可以“修改”,但还可以通过“插入”、“删除”使得S1变换为S2

  • 在S1字符串末位插入字符 “f”,此时S1 = ”abcf”,S2 = ”def”,此时即S1[i] = S2[j]的情况,S1变换为S2的编辑距离为d[4, 3] = d[3, 2]。所以得出d[i, j]=d[i, j - 1] + 1。(+1是因为S1新增了”f”)

  • 在S2字符串末位插入字符“c”,此时S1 = ”abc”,S2 = ”defc”,此时即S1[i] = S[j]的情况,S1变换为S2的编辑距离为d[3, 4] = d[2, 3]。所以得出d[i, j]=d[i - 1, j] + 1,实际上这是对S1做了删除。(+1是因为S2新增了”c”)

  • 将S1字符串末位字符修改为”f”,此时S1 = ”abf”,S2 = ”def”,此时即S1[i] = S[j]的情况,S1变换为S2的编辑距离为d[3, 3] = d[2, 2]。所以得出d[i, j] = d[i – 1, j - 1] + 1。(+1是因为S1修改了“c”)
    在这里插入图片描述
    不妨用表格表示出动态规划对S1=”abc”,S2=“def”的求解过程

在这里插入图片描述
可以看出红色方块即是最终所求的编辑距离,整个求解过程就是填满这个表——二维数组。


代码1:

'''
    动态规划——字符串的编辑距离
    s1 = "abc", s2 = "def"
    计算公式:
             | 0                                           i = 0, j = 0
             | j                                           i = 0, j > 0
    d[i,j] = | i                                           i > 0, j = 0
             | min(d[i,j-1]+1, d[i-1,j]+1, d[i-1,j-1])     s1(i) = s2(j)
             | min(d[i,j-1]+1, d[i-1,j]+1, d[i-1,j-1]+1)   s1(i) ≠ s2(j)
    定义二维数组[4][4]:
        d e f            d e f
      |x|x|x|x|        |0|1|2|3|
    a |x|x|x|x|  =>  a |1|1|2|3|  => 编辑距离d = [4][4] = 3
    b |x|x|x|x|      b |2|2|2|3|
    c |x|x|x|x|      c |3|3|3|3|
'''
def levenshtein(s1, s2):
    i = 0   # s1字符串中的字符下标
    j = 0   # s2字符串中的字符下标
    s1i = ""    # s1字符串第i个字符
    s2j = ""    # s2字符串第j个字符
    m = len(s1) # s1字符串长度
    n = len(s2) # s2字符串长度
    if m == 0:
        return n    # s1字符串长度为0,此时的编辑距离就是s2字符串长度
    if n == 0:
        return m    # s2字符串长度为0,此时的编辑距离就是s1字符串长度
    solutionMatrix = [[0 for col in range(n + 1)] for row in range(m + 1)]  # 长为m+1,宽为n+1的矩阵
    '''
             d e f
          |0|x|x|x|
        a |1|x|x|x|
        b |2|x|x|x|
        c |3|x|x|x|
    '''
    for i in range(m + 1):
        solutionMatrix[i][0] = i
    '''
             d e f
          |0|1|2|3|
        a |x|x|x|x|
        b |x|x|x|x|
        c |x|x|x|x|

    '''
    for j in range(n + 1):
        solutionMatrix[0][j] = j
    '''
        上面两个操作后,求解矩阵变为
             d e f
          |0|1|2|3|
        a |1|x|x|x|
        b |2|x|x|x|
        c |3|x|x|x|
        接下来就是填充剩余表格
    '''
    for x in range(1, m + 1):
        s1i = s1[x - 1]
        for y in range(1, n + 1):
            s2j = s2[y - 1]
            flag = 0 if s1i == s2j  else 1
            solutionMatrix[x][y] = min(solutionMatrix[x][y-1] + 1, solutionMatrix[x-1][y] + 1, solutionMatrix[x-1][y-1] + flag)

    return solutionMatrix[m][n]

def min(insert, delete, edit):
    tmp = insert if insert < delete else delete
    return tmp if tmp < edit else edit

s1 = "abc"
s2 = "def"
distance = levenshtein(s1, s2)
print(distance)

代码2:

class Solution(object):
    def minDistance(self, word1, word2):

        m = len(word1)
        n = len(word2)
        if m == 0:
            return n
        if n == 0:
            return m
    
        dp = [[0]*(n+1) for i in range(m+1)]

        dp[0][0] = 0
        for i in range(1,m+1):
            dp[i][0] = i
        for j in range(1,n+1):
            dp[0][j] = j

        for i in range(1, m+1):
            for j in range(1, n+1):
                if word1[i-1] == word2[j-1]:  
                    dp[i][j] = dp[i-1][j-1]
                else:
                    dp[i][j] = 1+min(dp[i-1][j-1], dp[i-1][j], dp[i][j-1]) # 方格的左上,上,左最小值+1

        return dp[m][n]

s1 = "abc"
s2 = "def"
s = Solution()
distance = s.minDistance(s1, s2)
print(distance)

C++写法:

class Solution {
public:
    int minDistance(string word1, string word2) {
        int n = word1.size(), m = word2.size();
        vector<vector<int>> f(n+1, vector<int>(m+1));
        
        // 初始化边界情况
        for(int i = 0; i <= n; i++) 
            f[i][0] = i;  // 第一个字符串的前i个字母变为第二个字符串的前0个(删除)
        for(int i = 0; i <= m; i++)
            f[0][i] = i;  // 第一个字符串的前0个字母变为第二个字符串的前i个(添加)

        for(int i = 1; i <= n; i++)
            for(int j = 1; j <= m; j++){
                f[i][j] = min(f[i][j-1]+1, f[i-1][j]+1); // 添加和删除的情况
                f[i][j] = min(f[i][j], f[i-1][j-1]+ (word1[i-1]  != word2[j-1]));
            }
        return f[n][m];
    }
};

参考链接:
动态规划(1)——字符串的编辑距离
leetcode–72–编辑距离


题目来源:
https://leetcode-cn.com/problems/edit-distance

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值