动态规划专题——leetcode 72. Edit Distance Hard(最小编辑距离)

1.题目描述

Given two strings word1 and word2, return the minimum number of operations required to convert word1 to word2.

You have the following three operations permitted on a word:

  • Insert a character
  • Delete a character
  • 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')

Constraints:

  • 0 <= word1.length, word2.length <= 500
  • word1 and word2 consist of lowercase English letters.

 

2.题解

方法1:自顶向下(递归 + 备忘录数组memo[][])

思路:对s1和s2字符串都是从后向前遍历,备忘录数组大小为[m+1][n+1],memo[i][j] 存储了word1(0...i-1)到 word2(0...j-1)的最小编辑距离。

(1)状态:原问题和子问题之间变化的变量,字符串1和字符串2比较长度的变化,即 i 和 j(遍历字符串1和字符串2的下标值);

(2)dp函数的定义:dp(i, j)表示 字符串1(0...i)  和 字符串2(0...j) 之间的最小编辑距离;

(3)确定选择并择优:分情况讨论:如果当前 word1[i] == word2[j],则跳过,进入dp(i-1, j-1),当前编辑距离为0;否则有3种操作,分别是 插入、删除、替换,比较者种操作的编辑距离,选择最小的存入备忘录memo;

(4)base case:若字符串1 先被遍历完,则 距离需要加上字符串2剩余长度(相当于在做插入操作),若字符串2 先被遍历完,则 距离需要加上字符串1剩余长度(相当于在做删除操作)。

class Solution {
    public int minDistance(String word1, String word2) {
        if(word1.length() == 0){
            return word2.length();
        }
        if(word2.length() == 0){
            return word1.length();
        }
        //开辟备忘录数组
        int[][] memo = new int[word1.length()+1][word2.length()+1];
        //初始化备忘录
        for(int i = 0; i < word1.length()+1; i++){
            for(int j = 0; j < word2.length()+1; j++){
                if(i == 0){
                    memo[0][j] = j;
                }else if(j == 0){
                    memo[i][0] = i;
                }else{
                    memo[i][j] = -1;
                }
            }
        }
        return dp(word1, word2, word1.length()-1, word2.length()-1, memo);
    }
    
    //递归:dp函数
    private int dp(String s1, String s2, int i, int j, int[][] memo){
        //s1字符串先遍历完,则插入剩余的s2字符串
        if(i == -1){
            return j+1;
        }
        //s2字符串先遍历完,则删除剩余的s1字符串
        if(j == -1){
            return i+1;
        }
        //如果备忘录里存有结果就直接返回
        if(memo[i+1][j+1] != -1){
            return memo[i+1][j+1];
        }
        // s1当前字符 != s2当前字符,比较“插入、删除、替换”3种操作的距离
        if(s1.charAt(i) != s2.charAt(j)){
            memo[i+1][j+1] = calcMin(
            dp(s1, s2, i-1, j-1, memo)+1,
            dp(s1, s2, i-1, j, memo)+1,
            dp(s1, s2, i, j-1, memo)+1);
        }else{
            //s1当前字符 == s2当前字符,跳过比较下一个
            memo[i+1][j+1] = dp(s1, s2, i-1, j-1, memo);
        } 
        return memo[i+1][j+1];
    }
    
    private int calcMin(int a , int b, int c){
        return Math.min(a, Math.min(b,c));
    }
}

时间复杂度:O(mn),其中m、n分别为word1、word2的长度;

空间复杂度:O(mn),memo数组大小加上递归栈。

 

方法2:自底向上(dp table)

思路:对s1和s2字符串都是从后向前遍历,dp数组大小为[m+1][n+1],dp[i][j] 存储了word1(0...i-1)到 word2(0...j-1)的最小编辑距离。

这里的dp数组和方法1中的dp函数有些类似。

class Solution {
    public int minDistance(String word1, String word2) {
        if(word1 == null || word2 == null){
            return 0;
        }
        int m = word1.length();
        int n = word2.length();
        if(m == 0){
            return n;
        }
        if(n == 0){
            return m;
        }
        
        int[][] dp = new int[m+1][n+1];
        //base case
        for(int i = 0; i <= m; i++){
            dp[i][0] = i;
        }
        for(int j = 0; j <= n; j++){
            dp[0][j] = j;
        }
        
        //遍历字符串1和字符串2,求最小编辑距离
        for(int i = 1; i <= m; i++){
            for(int j = 1; j <= n; j++){
                if(word1.charAt(i-1) == word2.charAt(j-1)){
                    dp[i][j] = dp[i-1][j-1];
                }else{
                    //对字符串1的操作
                    int insert = dp[i][j-1]+1;
                    int delete = dp[i-1][j]+1;
                    int replace = dp[i-1][j-1]+1; 
                    dp[i][j] = Math.min(insert, Math.min(delete, replace));
                }
            }
        }
        return dp[m][n];
    }
}

时间复杂度:O(mn),其中m、n分别为word1、word2的长度;

空间复杂度:O(mn),只有开辟dp数组的开销,没有递归函数的栈开销,比方法1开销小一点。

 

3.收获

1、从这道题开始理解什么是“自顶向下”、什么是“自底向上”。其实递归函数一般是“自顶向下”,因为是由当前原问题进入子问题的递归最后返回解,而dp数组这种动态规划,其实是在已知递归思路后想到的,可以从子问题开始求解,最后得到原问题的解,感觉方法2是方法1的升华,所以动态规划的转移方程以及dp数组定义比较难想到。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值