动态规划 leetcode 72. Edit Distance

刚开始做题,发现了一道动态规划的好题,看了答案 想了一段时间,来分享一波。(如果只看动态规划可以从下边分割线以下开始阅读)

原题在地址:https://leetcode.com/problems/edit-distance/ 原题粘贴如下:

Given two words word1 and word2, find the minimum number of operations required to convert word1 to word2.

You have the following 3 operations permitted on a word:

  1. Insert a character
  2. Delete a character
  3. Replace a character

Example 1:

Input: word1 = "horse", word2 = "ros"
Output: 3
Explanation: 
horse -> rorse (replace 'h' with 'r')
rorse -> rose (remove 'r')
rose -> ros (remove 'e')

Example 2:

Input: word1 = "intention", word2 = "execution"
Output: 5
Explanation: 
intention -> inention (remove 't')
inention -> enention (replace 'i' with 'e')
enention -> exention (replace 'n' with 'x')
exention -> exection (replace 'n' with 'c')
exection -> execution (insert 'u')

这是一道单词变换的题目,给出两个单词word1和word2,给出了三种变换方式,每次操作分别是删除、插入和改变word1上的一个字母,问最少需要多少次操作可以将word1变到word2。首先将大佬的动态规划答案贴出来,方便大家阅读。

Let following be the function definition :-

f(i, j) := minimum cost (or steps) required to convert first i characters of word1 to first j characters of word2

Case 1: word1[i] == word2[j], i.e. the ith the jth character matches.

f(i, j) = f(i - 1, j - 1)

Case 2: word1[i] != word2[j], then we must either insert, delete or replace, whichever is cheaper

f(i, j) = 1 + min { f(i, j - 1), f(i - 1, j), f(i - 1, j - 1) }

  1. f(i, j - 1) represents insert operation
  2. f(i - 1, j) represents delete operation
  3. f(i - 1, j - 1) represents replace operation

Here, we consider any operation from word1 to word2. It means, when we say insert operation, we insert a new character after word1 that matches the jth character of word2. So, now have to match i characters of word1 to j - 1 characters of word2. Same goes for other 2 operations as well.

Note that the problem is symmetric. The insert operation in one direction (i.e. from word1 to word2) is same as delete operation in other. So, we could choose any direction.

Above equations become the recursive definitions for DP.

Base Case:

f(0, k) = f(k, 0) = k

Below is the direct bottom-up translation of this recurrent relation. It is only important to take care of 0-based index with actual code :-

 

public class Solution {
    public int minDistance(String word1, String word2) {
        int m = word1.length();
        int n = word2.length();
        
        int[][] cost = new int[m + 1][n + 1];
        for(int i = 0; i <= m; i++)
            cost[i][0] = i;
        for(int i = 1; i <= n; i++)
            cost[0][i] = i;
        
        for(int i = 0; i < m; i++) {
            for(int j = 0; j < n; j++) {
                if(word1.charAt(i) == word2.charAt(j))
                    cost[i + 1][j + 1] = cost[i][j];
                else {
                    int a = cost[i][j];
                    int b = cost[i][j + 1];
                    int c = cost[i + 1][j];
                    cost[i + 1][j + 1] = a < b ? (a < c ? a : c) : (b < c ? b : c);
                    cost[i + 1][j + 1]++;
                }
            }
        }
        return cost[m][n];
    }
}

 

Time complexity : If n is the length of word1, m of word2, because of the two indented loops, it is O(nm)

我一看题目发现自己并不会,而且没有思路。于是翻看在discuss区的答案,发现答案也并不是很好懂,于是开始把答案粘贴到eclipse然后用单步调试看程序的运行结果,并在Excel上作图,帮助理解。首先我根据调试的结果画出了cost[][]数组的变化规律,

上图是答案创建数组的变化规律,我最开始没看懂的是为什么从左上角变化到右下角就一定可以使单词word1变化到word2,后来我发现他创建数组是m+1行,n+1列的。竖直向下走一行是插入一个元素,水平向右走一行是删除一个元素,答案现将数组的第一行初始化为0123,又将第一列初始化为012345,代表着每删除或插入一个元素,需要一步,而且而且这种变换只有一种可能,不会出现插入,删除,变换三种选择总步数最少的情况。每次从cost[i][j]到cost[i+1][j+1],代表着通过最少的步数把word1的第i位变成了word2的第j位所需的最少总步数。但是如何保证当word1和word2的长度不同时还依然可以使两者转换成功呢?因为创建数组为第一个单词长度+1行,第二个长度+1列,于是乎当坐标从左上角走到右下角时,必然会向下走5行,同时向右走三列,这样就必然会删除5个字母同时插入3个字母,合计删除2个字母,所以字母数目从5个变化到了3个,但是当斜着走的时候怎么算呢?斜着走的时候可以看做在对应位置上先插入一个字母,然后保留新插入的字母再将原来的字母进行删除,所以斜着走相当于单词的长度没有变化,而且如果是斜着走一定会发现想走到右下角一定是走的总行数比列数多2,所以字符串长度一定会变到相等。

当我们发现长度最终一定会相等时,还会发现另一个问题,从左上角变到右下角一定会从word1变化到word2么?

答案也是肯定的,我们先不考虑总步数,暂时先考虑最特殊的情况,如上图所示按红色单词顺序从左上角走到右下角,这时正好可以将word1转换为word2,即先删掉word1,再插入word2。

也可像下图所示先横着走再竖着走。结果一定会从word1变成word2。

其实可以斜着看,每斜着走一步就是把word1和word2上对应位置上的字母变成相同字母,也就是说斜着走三步从cost[0][0]

到cost[3][3]的时候,前三个字母已经一样了,剩下的只是删掉多余字母(当word1长度大于word2时),或是在尾部插入相差长度的字母,使得两者的最终长度相同。而每次斜着走都是可以分解为一次横着走和一次竖着走的,所以最终还是等效于从cost[0][0]走到cost[3][3]就是把前三位变成了相同的字母,剩下的步骤就是删掉后面多余字母或者在后面插入缺失的字母。

即无论怎么走最终都是可以将word1变到word2的。只是有一个前提条件当你走到cost[i+1][j+1]时,一定要满足word1上的第i位置上的元素和word2上第j位置上的元素是一致的,即每次你斜着走一步时,后者下走一步右走一步时,一定使word1中的一个字母与word2中相同,无论什么位置(因为位置不对是可以通过插入删除改变的)。

———————————分割线如果明白为什么无论怎么走到最后一定可以成功转换的可以直接读以下部分—————————

现在已经确定了从左上走到右下时,是可以让word1完全转化为word2的。那么下面该讨论这个问题的重点了,如何使用动态规划完成最少步数的计算?

动态规划其实就是将要求解的问题转化成不同部分的子问题,当这个问题的所有子问题解决解决后,最终就组合成了这个问题的解,而在解决子问题的时候,子问题之间是会存在重叠部分的,所以一般通过存储子问题的解来避免重复计算子问题中的重叠部分,进而减少计算量。

在本题中答案给出的方法是使用一个(M+1)*(N+1)的int数组存储转换的最少步数,第一行和第一列由于没有选择(删除,转换,插入),只有一种情况,所以每个格递增1。

本题的问题是如何求到最小的cost[M+1][N+1],可以转化为两种情况,当word1的第M个元素和word2的第N个元素相等时,问题转化为求最小的cost[M][N],当不等时,需要在cost[M][N]、cost[M+1][N]和cost[M][N+1]中选择一个最小的然后操作一步,以此类推最后会求出cost矩阵上每一个点的最小值然后反推回来形成cost[M+1][N+1]。所以需要反向来进行求解,依次求出每个位置上的最小值,组合起来就是最后的答案。

答案是通过做双重循环进行依次求每个格的最小值。结果如第一张图所示为了方便阅读我粘过来。

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值