代码随想录刷题day56|2个字符串的删除操作&编辑距离


day56学习内容

day56主要内容

  • 2个字符串的删除操作
  • 编辑距离

声明
本文思路和文字,引用自《代码随想录》


一、2个字符串的删除操作

583.原题链接

LCS:求解两个字符串的最长公共子序列

1.1、动态规划五部曲

1.1.1、 确定dp数组(dp table)以及下标的含义

  • dp[i][j]表示字符串 word1 的前 i 个字符和字符串 word2 的前 j 个字符的最长公共子序列的长度。

1.1.2、确定递推公式

考虑两种情况:

  1. 字符匹配情况

    • 如果 word1 的第 i 个字符和 word2 的第 j 个字符相同,即 word1[i-1] == word2[j-1](因为字符串的索引从0开始),那么这两个字符可以成为LCS的一部分。
    • 在这种情况下,这两个字符增加了LCS的长度,因此 dp[i][j] = dp[i-1][j-1] + 1。这表明我们将这两个匹配的字符加入到LCS中,从而增加LCS的长度。
  2. 字符不匹配情况

    • 如果 word1[i-1]word2[j-1] 不相同,我们需要从两种选择中选取较优的一种,即不包括 word1 的第 i 个字符或不包括 word2 的第 j 个字符。
    • 因此,我们可以考虑两种情况,一种是保留 word1 的前 i-1 个字符和 word2 的前 j 个字符的LCS(即不包括 word1[i-1]),另一种是保留 word1 的前 i 个字符和 word2 的前 j-1 个字符的LCS(即不包括 word2[j-1])。这样,dp[i][j] = max(dp[i-1][j], dp[i][j-1])

1.1.3、 dp数组如何初始化

  • i = 0j = 0 时,即一个字符串的长度为0,显然最长公共子序列的长度也为0,因为空字符串与任何字符串的公共子序列也是空,所以 dp[0][j] = 0dp[i][0] = 0

1.1.4、确定遍历顺序

从小到大遍历

1.1.5、输出结果

  • 最终的最长公共子序列长度存储在 dp[len1][len2]
  • 最小编辑距离计算为 len1 + len2 - 2 * dp[len1][len2]。这个公式的由来是:len1 - dp[len1][len2] 表示 word1 需要删除的字符数,len2 - dp[len1][len2] 表示需要向 word1 插入 word2 中的字符数,因此总编辑距离是两者之和。

1.2、代码

class Solution {
    public int minDistance(String word1, String word2) {
        // 获取两个字符串的长度
        int len1 = word1.length();
        int len2 = word2.length();

        // 创建一个二维数组dp,用于存储最长公共子序列的长度
        int[][] dp = new int[len1 + 1][len2 + 1];

        // 初始化dp数组的第0行和第0列,因为任何字符串与空字符串的LCS长度为0
        for (int i = 0; i <= len1; i++) {
            dp[i][0] = 0;
        }
        for (int j = 0; j <= len2; j++) {
            dp[0][j] = 0;
        }

        // 填充dp数组
        for (int i = 1; i <= len1; i++) {
            for (int j = 1; j <= len2; j++) {
                // 如果当前两个字符相同,增加LCS的长度
                if (word1.charAt(i - 1) == word2.charAt(j - 1)) {
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                } else {
                    // 如果不相同,取两种可能的最大LCS长度
                    dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
                }
            }
        }

        // 计算将一个字符串转换成另一个字符串的最小编辑次数
        // 总字符数减去两倍的LCS长度
        return len1 + len2 - dp[len1][len2] * 2;
    }
}

二、编辑距离

72.原题链接

编辑距离是指将一个字符串转换成另一个字符串所需的最小单字符编辑操作次数,包括插入、删除和替换操作。

2.1、动态规划五部曲

2.1.1、 确定dp数组(dp table)以及下标的含义

  • 其中dp[i][j] 表示word1 的前 i 个字符转换成 word2 的前 j 个字符所需的最小编辑距离。

2.1.2、确定递推公式

定义 dp[i][j] 为字符串 word1 的前 i 个字符到字符串 word2 的前 j 个字符的最小编辑距离。这里 word1[0...i-1]word2[0...j-1] 分别表示字符串 word1word2 的子串。

  1. 初始化

    • dp[i][0] = i 表示将 word1 的前 i 个字符全部删除到空字符串的编辑距离,需要 i 次删除。
    • dp[0][j] = j 表示从空字符串插入到 word2 的前 j 个字符的编辑距离,需要 j 次插入。
  2. 填充表格

    • 对于每一对字符 word1[i-1]word2[j-1],我们考虑以下情况:
      • 如果两个字符相同(word1[i-1] == word2[j-1]),那么不需要额外操作,此时 dp[i][j] = dp[i-1][j-1]
      • 如果两个字符不同,我们可以进行三种操作:
        • 替换:将 word1[i-1] 替换为 word2[j-1],使其相同,此时考虑的状态为 dp[i-1][j-1],操作后成本增加1,即 dp[i][j] = dp[i-1][j-1] + 1
        • 插入:在 word1 中的位置 i 插入 word2[j-1],此时 word2 的位置 j 已被匹配,考虑的状态为 dp[i][j-1],操作后成本增加1,即 dp[i][j] = dp[i][j-1] + 1
        • 删除:删除 word1 中的字符 word1[i-1],此时考虑的状态为 dp[i-1][j],操作后成本增加1,即 dp[i][j] = dp[i-1][j] + 1
      • 选择上述三种操作中的最小成本作为 dp[i][j] 的值。

2.1.3、 dp数组如何初始化

  • 字符串长度mn 分别是 word1word2 的长度。
  • 动态规划数组 dp:大小为 (m+1) x (n+1) 的二维数组,dp[i][j] 表示 word1 的前 i 个字符转换成 word2 的前 j 个字符所需的最小编辑距离。
  • 初始化行和列
  • 第一列:dp[i][0] 表示把 word1 的前 i 个字符全部删除变为空字符串所需的最小操作次数,因此为 i
  • 第一行:dp[0][j] 表示从空字符串通过插入操作变成 word2 的前 j 个字符所需的最小操作次数,因此为 j

2.1.4、确定遍历顺序

  1. 初始化阶段

    • 第一列初始化:从 i = 1i = m。这一步设置了从空字符串到 word1 的前 i 个字符的转换,每步都需要一次删除操作。
    • 第一行初始化:从 j = 1j = n。这一步设置了从空字符串到 word2 的前 j 个字符的转换,每步都需要一次插入操作。
  2. 填充动态规划表

    • 双层循环遍历
      • 外层循环(对 word1 的遍历):从 i = 1i = m。这个循环逐个考虑 word1 的每一个字符,直到最后一个字符。
      • 内层循环(对 word2 的遍历):从 j = 1j = n。在每次外层循环中,这个内层循环逐个考虑 word2 的每一个字符,直到最后一个字符。

2.1.5、输出结果

  • 最后一个元素dp[m][n] 表示将 word1 完全转换为 word2 的最小编辑距离。

2.2、代码

class Solution {
    public int minDistance(String word1, String word2) {
        int m = word1.length(); // 获取 word1 的长度
        int n = word2.length(); // 获取 word2 的长度

        // 创建动态规划表,大小为 (m+1) x (n+1)
        int[][] dp = new int[m + 1][n + 1];

        // 初始化第一列,从空字符串变为 word1 的前 i 个字符需要 i 次删除
        for (int i = 1; i <= m; i++) {
            dp[i][0] = i;
        }

        // 初始化第一行,从空字符串变为 word2 的前 j 个字符需要 j 次插入
        for (int j = 1; j <= n; j++) {
            dp[0][j] = j;
        }

        // 填充 dp 表
        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 {
                    // 如果字符不同,计算替换、插入和删除操作的最小成本
                    // dp[i-1][j-1] + 1: 替换 word1[i-1] 使其变为 word2[j-1]
                    // dp[i][j-1] + 1: 在 word1 中插入 word2[j-1]
                    // dp[i-1][j] + 1: 从 word1 中删除字符 word1[i-1]
                    dp[i][j] = Math.min(Math.min(dp[i - 1][j - 1], dp[i][j - 1]), dp[i - 1][j]) + 1;
                }
            }
        }

        // 最终的 dp[m][n] 就是从 word1 变为 word2 所需的最小编辑次数
        return dp[m][n];
    }
}

总结

1.感想

  • 明天就要结束动态规划了。。刷了半个月

2.思维导图

本文思路引用自代码随想录,感谢代码随想录作者。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值