72.编辑距离(动态规划)

题目

72.编辑距离

动态规划

首先来说是动态规划(Dynamic Programing),说实话,我以前真没有完全理解并运用它,只是简单看了点皮毛,但在最近刷题时,发现很多题目运用动态规划很简单就解决了,也就激发了我学习并写这篇文章的兴趣。

我在网上看有大佬说过90%的字符串类型的问题都可以运用动态规划解决。

下面就说说动态规划的几大要素,动态规划就是用历史记录来解当前的解,避免重复计算,而这些历史记录需要通过变量来保存,通常是一维或二维数组,定义为dp,动态规划主要分为四大步骤:

  1. 定义状态,通俗点讲就是定义数组元素的含义,前面说过,我们要用一个数组来保存历史记录,这一步的关键就是要找出这个数组元素的含义,它代表什么?通常不太容易想到我们定义定义的数组的含义;
  2. 思考状态转移方程,即找出数组元素之间的关系,有点像递归,当我们要计算dp[n]时,一般都和dp在n之前的值有关系,这是最难的一步;

技巧是分类讨论。对状态空间进行分类,思考最优子结构到底是什么。即大问题的最优解如何由小问题的最优解得到。

  1. 初始化,找出初始值,一般一维数组通常是前两三个数,二位数组则是分别当行i或列j为0时需要初始化;
  2. 思考输出值,也叫返回值,就是我们要得到的结果要通过数组如何得到。

所以本题的思路:

  • 「动态规划」告诉我们可以「自底向上」去考虑一个问题,思路是:先想这个问题最开始是什么情况,这个问题是两个字符串都为空字符的时候,然后逐个地,一个字符一个字符加上去,在加字符的过程中考虑「状态转移」;
  • 由于要考虑空字符,因此状态空间要多设置一行、多设置一列。

第一步 定义状态

定义dp[i][j],表示word1 中前 i 个字符,变换到 word2 中前 j 个字符,最短需要操作的次数,同时还需要考虑当word1 或 word2 为空时的情况,所以需要预留dp[0][j] 和 dp[i][0]。

第二步 状态转移方程

(一)、当word1[i]==word2[j]时,由于遍历到了i和j,说明word1的0~i-1和word2的 0 ~j-1的匹配结果已经生成,由于当前两个字符相同,因此无需做任何操作,dp[i][j]=dp[i-1][j-1]

(二)、当word1[i]!=word2[j]时,可以进行的操作有3个:

  • 替换操作:可能word1的0~i-1位置与word2的 0~j-1位置的字符都相同,只是当前位置的字符不匹配,进行替换操作后两者变得相同,所以此时dp[i][j]=dp[i-1][j-1]+1(这个加1代表执行替换操作);

  • 删除操作:若此时word1的0~i-1位置 与word2的0~j位置已经匹配了,此时多出了word1的i位置字符,应把它删除掉,才能使此时word1的 0~i(这个i是执行了删除操作后新的i)和word2 的0~j位置匹配,因此此时dp[i][j]=dp[i-1][j]+1(这个加1代表执行删除操作);

  • 插入操作:若此时word1的0~i 位置只是和word2的0~j-1位置匹配,此时只需要在原来的i位置后面插入一个 和word2的j位置相同的字符使得此时的word1的0~i(这个i是执行了插入 操作后新的i)和word2的0~j匹配得上,所以此时dp[i][j]=dp[i][j-1]+1(这个加1代表执行插入操作);

  • 由于题目所要求的是要最少的操作数:所以当word1[i] != word2[j] 时, 需要在这三个操作中选取一个最小的值赋格当前的dp[i][j]。

,dp[i][j] = dp[i][j - 1] + 1
,dp[i][j] = dp[i - 1][j] + 1
,dp[i][j] = dp[i - 1][j - 1] + 1

(三)总结:状态方程为:

if(word1[i] == word2[j]):
      dp[i][j] = dp[i-1][j-1]
else:
       min(dp[i-1][j-1],dp[i-1][j],dp[i][j-1])+1

第三步 初始化

  • 从一个字符串变为空串,其编辑距离就是当前字符串长度的距离;
  • 从一个空串变为非空串其编辑距离同上

第四步 思考输出

输出dp[m][n],即为word1转换为word2的最少操作步数。

下面是已题目例子进行的二维数组表格:

‘’ros
‘’0123
h1123
o2212
r3222
s4332
e5443

代码

class Solution {
    public int minDistance(String word1, String word2) {
        if(word1 == null &&  word2 == null) return 0;
        int m = word1.length(),n = word2.length();
        int[][] dp = new int[m+1][n+1]; //dp[i][j]表示word1从前i个字符变为 word2的前j个字符所需要的最少步数
        //dp[0][0] =0; 初始化当i和j分别为0时(边界条件)的步数
        for(int i = 1;i <= m;i++) //j为0,说明最后要将word2变为空,每次删除一个字符即可
            dp[i][0] = dp[i-1][0] + 1;
        for(int j = 1;j <= n;j++)//i为0,说明当word1为空时,通过一步步插入一个字符转换为word2
            dp[0][j] = dp[0][j-1] + 1;

        for(int i = 1;i <= m ;i++){
            for(int j = 1;j <= n;j++ ){
                // 如果 word1[i] 与 word2[j] 相等。第 i 个字符对应下标是 i-1
                //当两个字符串当前位置ij对应的字符相等时,不需要通过字符的变更,值就是dp[i-1][j-1]
                if(word1.charAt(i-1) == word2.charAt(j-1)) 
                    dp[i][j] = dp[i-1][j-1];
                else
                //否则取其左,上,左上三个当中的最小值
                    dp[i][j] = Math.min(dp[i-1][j-1],Math.min(dp[i-1][j],dp[i][j-1])) + 1;
            }
        }
        return dp[m][n];
    }
}

复杂度
时间复杂度:O(MN),M为word1的长度,N为word2的长度,双层循环
空间复杂度:O(MN),用了一个二维数组来记录状态值

参考
https://www.zhihu.com/question/23995189

http://cppblog.com/menjitianya/archive/2015/10/23/212084.html

https://leetcode-cn.com/problems/edit-distance/solution/dong-tai-gui-hua-java-by-liweiwei1419/

https://leetcode-cn.com/problems/longest-palindromic-substring/solution/zhong-xin-kuo-san-dong-tai-gui-hua-by-liweiwei1419/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值