力扣刷题day48|583两个字符串的删除操作、72编辑距离

文章目录

    • 583. 两个字符串的删除操作
      • 动态规划思路一
      • 动态规划思路二
        • 动态规划五部曲
    • 72. 编辑距离
      • 思路
        • 动态规划五部曲

583. 两个字符串的删除操作

力扣题目链接

给定两个单词 word1word2 ,返回使得 word1word2 相同所需的最小步数

每步 可以删除任意一个字符串中的一个字符。

示例 1:

输入: word1 = "sea", word2 = "eat"
输出: 2
解释: 第一步将 "sea" 变为 "ea" ,第二步将 "eat "变为 "ea"

示例 2:

输入:word1 = "leetcode", word2 = "etco"
输出:4

动态规划思路一

本题和动态规划:115.不同的子序列基本相同,只要求出两个字符串的最长公共子序列长度即可,那么除了最长公共子序列之外的字符都是必须删除的,最后用两个字符串的总长度减去两个最长公共子序列的长度就是删除的最少步数。

public int minDistance(String word1, String word2) {
    // 初始化
    int[][] dp = new int[word1.length() + 1][word2.length() + 1];

    for (int i = 1; i <= word1.length(); i++) {
        for (int j = 1; j <= word2.length(); j++) {
            if (word1.charAt(i - 1) == word2.charAt(j - 1)) {
                dp[i][j] = dp[i - 1][j - 1] + 1;
            }else {
                dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
            }
        }
    }

    return word1.length() + word2.length() - dp[word1.length()][word2.length()] * 2;
}

动态规划思路二

本题和动态规划:115.不同的子序列相比,这次是两个字符串可以相互删了

动态规划五部曲

  1. 确定dp数组以及下标的含义

dp[i][j]:以i-1为结尾的字符串word1,和以j-1位结尾的字符串word2,想要达到相等,所需要删除元素的最少次数。

  1. 确定递推公式
  • 当word1[i - 1] 与 word2[j - 1]相同的时候,只有一种情况:

此时说明当前的两个字符相同就不需要删除,就看两个字符串的再往前一个数字是否一样,即dp[i][j] = dp[i - 1][j - 1]

  • 当word1[i - 1] 与 word2[j - 1]不相同的时候,有种情况可以讨论:
    • 情况一:word1[i - 1],此时先对word1做一次删除操作,然后比较的就是word1[i - 2]word2[j - 1],即最少操作次数为dp[i][j] = dp[i - 1][j] + 1
    • 情况二:word2[j - 1],此时先对word2做一次删除操作,然后比较的就是word1[i - 1]word2[j - 2],即最少操作次数为dp[i][j] = dp[i][j - 1] + 1
    • 情况三:同时删word1[i - 1]word2[j - 1],操作的最少次数为dp[i - 1][j - 1] + 2

那最后当然是取最小值,所以当word1[i - 1] 与 word2[j - 1]不相同的时候,递推公式:dp[i][j] = min({dp[i - 1][j - 1] + 2, dp[i - 1][j] + 1, dp[i][j - 1] + 1})

因为dp[i - 1][j - 1] + 1等于 dp[i - 1][j] dp[i][j - 1],这里说明情况三包含在情况一和情况二里面(即可能先删**word1[i-1],再比较word1[i-2]word2[j-1]后删除了word2[j-1];或者是先删word2[j-1],再比较word2[j-2]word1[i-1]后删除了word1[i-1]**)

所以递推公式可简化为:dp[i][j] = min(dp[i - 1][j] + 1, dp[i][j - 1] + 1)

  1. dp数组如何初始化

从递推公式中,可以看出来,dp[i][0] dp[0][j]是一定要初始化的。

dp[i][0]:word2为空字符串,以i-1为结尾的字符串word1要删除多少个元素,才能和word2相同呢,很明显要删除i个字符,即dp[i][0] = i

dp[0][j]的话同理,所以代码如下:

for (int i = 0; i < word1.length() + 1; i++) dp[i][0] = i;
for (int j = 0; j < word2.length() + 1; j++) dp[0][j] = j;
  1. 确定遍历顺序

从递推公式 dp[i][j] = min(dp[i - 1][j - 1] + 2, min(dp[i - 1][j], dp[i][j - 1]) + 1); dp[i][j] = dp[i - 1][j - 1]可以看出dp[i][j]都是根据左上方、正上方、正左方推出来的。

所以遍历的时候一定是从上到下从左到右,这样保证dp[i][j]可以根据之前计算出来的数值进行计算。

  1. 举例推导dp数组

以示例一:word1:“sea”,word2:"eat"为例,推导dp数组状态图如下:

力扣刷题day48|583两个字符串的删除操作、72编辑距离_第1张图片

完整代码:

public int minDistance(String word1, String word2) {
    int[][] dp = new int[word1.length() + 1][word2.length() + 1];
    // 初始化
    for (int i = 0; i <= word1.length(); i++) {
        dp[i][0] = i;
    }
    for (int j = 0; j <= word2.length(); j++) {
        dp[0][j] = j;
    }

    for (int i = 1; i <= word1.length(); i++) {
        for (int j = 1; j <= word2.length(); j++) {
            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, dp[i][j - 1] + 1);
            }
        }
    }

    return dp[word1.length()][word2.length()];
}

72. 编辑距离

力扣题目链接

给你两个单词 word1word2请返回将 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')

思路

本体涉及到删除替换插入三个操作,就要分情况思考什么时候删除,什么时候替换以及什么时候插入呢?

动态规划五部曲

  1. 确定dp数组以及下标的含义

同样的和上一题相同,定义dp数组为

dp[i][j] 表示以下标i-1为结尾的字符串word1,和以下标j-1为结尾的字符串word2,最近编辑距离为dp[i][j]

  1. 确定递推公式
  • 如果word1[i-1] == word2[j-1],此时不需要任何操作,所以dp[i][j] = dp[i-1][j-1]

  • 如果word1[i-1] != word2[j-1],此时可以分为种情况:

    • 操作一:删除。word1删除一个元素,那么就是以下标i - 2为结尾的word1 与 j-1为结尾的word2的最近编辑距离,再加上一个操作,即 dp[i][j] = dp[i - 1][j] + 1;

    • 操作二:替换。替换word1[i - 1],使其与word2[j - 1]相同,此时不用增加元素,替换完后word1[i - 1]word2[j - 1]就一样的了。那么此时的编辑距离就是以下标i-2为结尾的word1 与 j-2为结尾的word2的最近编辑距离,加上一个替换元素的操作,即 dp[i][j] = dp[i - 1][j - 1] + 1;

    • 操作二:插入。在word1[i - 1]后面插入一个与word2[j - 1]相同的字符,此时要比较word2[i - 1]的前一个字符word2[i - 2]与当前的word1[i - 1]的关系(看他们的编辑距离),加上一个插入元素的操作,即 dp[i][j] = dp[i][j - 1] + 1;

三种情况取最小值,即:dp[i][j] = min({dp[i - 1][j], dp[i - 1][j - 1], dp[i][j - 1]}) + 1;

  1. dp数组如何初始化

再回顾一下dp[i][j]的定义:dp[i][j] 表示以下标i-1为结尾的字符串word1,和以下标j-1为结尾的字符串word2,最近编辑距离为dp[i][j]

dp[i][0] = iword1(0~i-1)要把其中的元素全部删除才能和此时为空集word2相同,所以要删除i次。

dp[0][j] = jword2(0~j-1)要把其中的元素全部删除才能和此时为空集word1相同,所以要删除j次。

for (int i = 1; i <= m; i++) {
    dp[i][0] =  i;
}
for (int j = 1; j <= n; j++) {
    dp[0][j] = j;
}
  1. 确定遍历顺序

由四个递推公式可以知道:

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

可以看出dp[i][j]是依赖左方,上方和左上方元素的

力扣刷题day48|583两个字符串的删除操作、72编辑距离_第2张图片

所以一定是从左到右从上到下去遍历。

  1. 举例推导dp数组

以示例1为例,输入:word1 = “horse”, word2 = "ros"为例,dp矩阵状态图如下:

力扣刷题day48|583两个字符串的删除操作、72编辑距离_第3张图片

完整代码:

public int minDistance(String word1, String word2) {
    int[][] dp = new int[word1.length() + 1][word2.length() + 1];
    // 初始化
    for (int i = 0; i <= word1.length(); i++) {
        dp[i][0] = i;
    }
    for (int j = 0; j <= word2.length(); j++) {
        dp[0][j] = j;
    }

    for (int i = 1; i <= word1.length(); i++) {
        for (int j = 1; j <= word2.length(); j++) {
            if (word1.charAt(i - 1) == word2.charAt(j - 1)) {
                dp[i][j] = dp[i - 1][j - 1];
            }else {
                dp[i][j] = Math.min(Math.min(dp[i - 1][j] + 1, dp[i - 1][j - 1] + 1), dp[i][j - 1] + 1);
            }
        }
    }

    return dp[word1.length()][word2.length()];
}

你可能感兴趣的:(leetcode,leetcode,算法,职场和发展)