代码随想录-Day51

115. 不同的子序列

给你两个字符串 s 和 t ,统计并返回在 s 的 子序列 中 t 出现的个数,结果需要对 109 + 7 取模。

示例 1:

输入:s = “rabbbit”, t = “rabbit”
输出:3
解释:
如下所示, 有 3 种可以从 s 中得到 “rabbit” 的方案。
rabbbit
rabbbit
rabbbit
示例 2:

输入:s = “babgbag”, t = “bag”
输出:5
解释:
如下所示, 有 5 种可以从 s 中得到 “bag” 的方案。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

class Solution {
    public int numDistinct(String s, String t) {
        int[][] dp = new int[s.length() + 1][t.length() + 1];
        for (int i = 0; i < s.length() + 1; i++) {
            dp[i][0] = 1;
        }
        
        for (int i = 1; i < s.length() + 1; i++) {
            for (int j = 1; j < t.length() + 1; j++) {
                if (s.charAt(i - 1) == t.charAt(j - 1)) {
                    dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
                }else{
                    dp[i][j] = dp[i - 1][j];
                }
            }
        }
        
        return dp[s.length()][t.length()];
    }
}

这段代码是用于解决「不同子序列」问题的Java实现,实质上是计算字符串 t 在字符串 s 中的不同子序列出现的次数。给定两个字符串 st,目标是找出 ts 中有多少种不同的子序列出现方式。

代码解析

  1. 初始化动态规划数组:

    • 创建一个二维数组 dp,其大小为 (s.length() + 1) x (t.length() + 1)dp[i][j] 的值代表 s 的前 i 个字符和 t 的前 j 个字符组成的子序列的出现次数。额外的一列是为了方便边界条件的处理,其中 dp[i][0] 的值默认为1,因为任何字符串都是空字符串的子序列,且只有一种方式。
  2. 动态规划迭代:

    • 从1到 s.length() 遍历 s 的每个字符,从1到 t.length() 遍历 t 的每个字符。
    • 对于 s 的第 i 个字符和 t 的第 j 个字符:
      • 如果两个字符相等,那么 s 的前 i 个字符和 t 的前 j 个字符组成的子序列的出现次数等于 s 的前 i-1 个字符和 t 的前 j-1 个字符组成的子序列的出现次数加上 s 的前 i-1 个字符和 t 的前 j 个字符组成的子序列的出现次数,即 dp[i - 1][j - 1] + dp[i - 1][j]
      • 如果两个字符不相等,那么出现次数等于 s 的前 i-1 个字符和 t 的前 j 个字符组成的子序列的出现次数,即 dp[i - 1][j]
  3. 返回结果:

    • 最后返回 dp[s.length()][t.length()],即 ts 中的不同子序列出现的次数。

时间复杂度和空间复杂度

  • 时间复杂度: O(m * n),其中 m 和 n 分别是字符串 st 的长度。这是因为需要遍历两个字符串的所有可能的字符组合来计算子序列的出现次数。
  • 空间复杂度: O(m * n),需要一个大小为 (m + 1) x (n + 1) 的动态规划数组 dp 来存储中间结果。

总结

这段代码通过动态规划的方法,有效地解决了不同子序列计数问题。尽管时间复杂度和空间复杂度较高,但在许多实际应用场景中,这样的性能通常是可接受的,特别是当字符串长度适中时。如果需要进一步优化空间复杂度,可以考虑使用滚动数组技术,将空间复杂度降低到 O(min(m, n)),但这通常会增加代码的复杂度。在处理此类问题时,权衡时间和空间资源是一个常见的考量因素。

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

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

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

示例 1:

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

输入:word1 = “leetcode”, word2 = “etco”
输出:4
在这里插入图片描述

方法一:

// dp数组中存储word1和word2最长相同子序列的长度
class Solution {
    public int minDistance(String word1, String word2) {
        int len1 = word1.length();
        int len2 = word2.length();
        int[][] dp = new int[len1 + 1][len2 + 1];

        for (int i = 1; i <= len1; i++) {
            for (int j = 1; j <= len2; 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 len1 + len2 - dp[len1][len2] * 2;
    }
}

这段代码是用于解决「编辑距离之最短编辑脚本」问题的Java实现,但其计算方法并不直接针对编辑距离,而是利用了最长公共子序列(LCS, Longest Common Subsequence)的概念。给定两个字符串 word1word2,目标是计算将 word1 转换为 word2 所需的最少编辑操作数,这里的编辑操作包括插入、删除和替换一个字符。

代码解析

  1. 初始化动态规划数组:

    • 创建一个二维数组 dp,其大小为 (len1 + 1) x (len2 + 1)dp[i][j] 的值代表 word1 的前 i 个字符和 word2 的前 j 个字符的最长公共子序列的长度。额外的一列和一行是为了方便边界条件的处理。
  2. 动态规划迭代:

    • 从1到 len1 遍历 word1 的每个字符,从1到 len2 遍历 word2 的每个字符。
    • 对于 word1 的第 i 个字符和 word2 的第 j 个字符:
      • 如果两个字符相等,那么 word1 的前 i 个字符和 word2 的前 j 个字符的最长公共子序列的长度等于 word1 的前 i-1 个字符和 word2 的前 j-1 个字符的最长公共子序列的长度加1,即 dp[i-1][j-1] + 1
      • 如果两个字符不相等,那么最长公共子序列的长度等于 word1 的前 i-1 个字符和 word2 的前 j 个字符的最长公共子序列的长度与 word1 的前 i 个字符和 word2 的前 j-1 个字符的最长公共子序列的长度中的较大值,即 Math.max(dp[i-1][j], dp[i][j-1])
  3. 计算编辑距离:

    • 最长公共子序列的长度可以被视为两个字符串共享的字符数量。为了将 word1 转换成 word2,我们需要删除 word1 中不在公共子序列中的字符,并在适当位置插入 word2 中不在公共子序列中的字符。因此,编辑距离等于 word1word2 的长度之和减去两倍的最长公共子序列的长度,即 len1 + len2 - dp[len1][len2] * 2

时间复杂度和空间复杂度

  • 时间复杂度: O(m * n),其中 m 和 n 分别是字符串 word1word2 的长度。这是因为需要遍历两个字符串的所有可能的字符组合来计算最长公共子序列的长度。
  • 空间复杂度: O(m * n),需要一个大小为 (m + 1) x (n + 1) 的动态规划数组 dp 来存储中间结果。

总结

这段代码通过计算最长公共子序列并利用其长度来间接计算编辑距离,提供了一种有效的解决方案。尽管它并没有直接使用编辑距离的动态规划公式,但通过巧妙地利用最长公共子序列的概念,仍然达到了计算编辑距离的目的。这种间接的方法有时在处理某些变体问题时会更加灵活和直观。如果需要进一步优化空间复杂度,可以考虑使用滚动数组技术,将空间复杂度降低到 O(min(m, n)),但这可能会使代码变得更复杂。

方法二:

// dp数组中存储需要删除的字符个数
class Solution {
    public int minDistance(String word1, String word2) {
        int[][] dp = new int[word1.length() + 1][word2.length() + 1];
        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;
        
        for (int i = 1; i < word1.length() + 1; i++) {
            for (int j = 1; j < word2.length() + 1; 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] + 2,
                                        Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1));
                }
            }
        }
        
        return dp[word1.length()][word2.length()];
    }
}

这段代码是用于解决「编辑距离」问题的Java实现,该问题要求计算将字符串 word1 转换为字符串 word2 所需的最小编辑操作数。编辑操作包括插入、删除和替换一个字符。代码通过动态规划方法实现,直接计算最小编辑距离。

代码解析

  1. 初始化动态规划数组:

    • 创建一个二维数组 dp,其大小为 (word1.length() + 1) x (word2.length() + 1)dp[i][j] 的值代表将 word1 的前 i 个字符转换为 word2 的前 j 个字符所需的最小编辑操作数。额外的一列和一行是为了方便边界条件的处理,其中 dp[i][0] 的值初始化为 idp[0][j] 的值初始化为 j,因为将一个字符串转换为空字符串或从空字符串转换为一个字符串所需的操作数分别为字符串的长度。
  2. 动态规划迭代:

    • 从1到 word1.length() + 1 遍历 word1 的每个字符,从1到 word2.length() + 1 遍历 word2 的每个字符。
    • 对于 word1 的第 i 个字符和 word2 的第 j 个字符:
      • 如果两个字符相等,那么将 word1 的前 i 个字符转换为 word2 的前 j 个字符所需的最小编辑操作数等于将 word1 的前 i-1 个字符转换为 word2 的前 j-1 个字符所需的最小编辑操作数,即 dp[i - 1][j - 1]
      • 如果两个字符不相等,那么最小编辑操作数等于以下三种操作中的最小值:
        • word1 的第 i 个字符替换为 word2 的第 j 个字符,加上将 word1 的前 i-1 个字符转换为 word2 的前 j-1 个字符所需的最小编辑操作数加2(因为替换操作成本定义为2)。
        • 删除 word1 的第 i 个字符,加上将 word1 的前 i-1 个字符转换为 word2 的前 j 个字符所需的最小编辑操作数加1。
        • word1 的前 i 个字符后插入 word2 的第 j 个字符,加上将 word1 的前 i 个字符转换为 word2 的前 j-1 个字符所需的最小编辑操作数加1。
  3. 返回结果:

    • 最后返回 dp[word1.length()][word2.length()],即 word1 转换为 word2 所需的最小编辑操作数。

时间复杂度和空间复杂度

  • 时间复杂度: O(m * n),其中 m 和 n 分别是字符串 word1word2 的长度。这是因为需要遍历两个字符串的所有可能的字符组合来计算最小编辑操作数。
  • 空间复杂度: O(m * n),需要一个大小为 (m + 1) x (n + 1) 的动态规划数组 dp 来存储中间结果。

总结

这段代码通过动态规划方法,有效地解决了编辑距离问题,直接计算出将一个字符串转换为另一个字符串所需的最小编辑操作数。如果需要进一步优化空间复杂度,可以考虑使用滚动数组技术,将空间复杂度降低到 O(min(m, n)),但这可能会使代码变得更加复杂。在实际应用中,选择合适的优化策略取决于具体的需求和资源限制。

方法三:

//DP - longest common subsequence (用最長公共子序列反推)
class Solution {
    public int minDistance(String word1, String word2) {
        char[] char1 = word1.toCharArray();
        char[] char2 = word2.toCharArray();

        int len1 = char1.length;
        int len2 = char2.length;

        int dp[][] = new int [len1 + 1][len2 + 1];

        for(int i = 1; i <= len1; i++){
            for(int j = 1; j <= len2; j++){
                if(char1[i - 1] == char2[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 len1 + len2 - (2 * dp[len1][len2]);//和leetcode 1143只差在這一行。
    }
}

这段代码是用于解决「编辑距离」问题的Java实现,但它实际上是通过计算两个字符串 word1word2 的最长公共子序列(LCS, Longest Common Subsequence)的长度,然后基于这个信息来间接计算编辑距离。这种方法的思路是,编辑距离可以看作是两个字符串各自去除最长公共子序列部分后,剩下的字符数总和。

代码解析

  1. 初始化动态规划数组:

    • 创建一个二维数组 dp,其大小为 (len1 + 1) x (len2 + 1)dp[i][j] 的值代表 word1 的前 i 个字符和 word2 的前 j 个字符的最长公共子序列的长度。额外的一列和一行是为了方便边界条件的处理。
  2. 动态规划迭代:

    • 从1到 len1 遍历 word1 的每个字符,从1到 len2 遍历 word2 的每个字符。
    • 对于 word1 的第 i 个字符和 word2 的第 j 个字符:
      • 如果两个字符相等,那么 word1 的前 i 个字符和 word2 的前 j 个字符的最长公共子序列的长度等于 word1 的前 i-1 个字符和 word2 的前 j-1 个字符的最长公共子序列的长度加1,即 dp[i-1][j-1] + 1
      • 如果两个字符不相等,那么最长公共子序列的长度等于 word1 的前 i-1 个字符和 word2 的前 j 个字符的最长公共子序列的长度与 word1 的前 i 个字符和 word2 的前 j-1 个字符的最长公共子序列的长度中的较大值,即 Math.max(dp[i-1][j], dp[i][j-1])
  3. 计算编辑距离:

    • 最长公共子序列的长度可以被视为两个字符串共享的字符数量。为了将 word1 转换成 word2,我们需要删除 word1 中不在公共子序列中的字符,并在适当位置插入 word2 中不在公共子序列中的字符。因此,编辑距离等于 word1word2 的长度之和减去两倍的最长公共子序列的长度,即 len1 + len2 - (2 * dp[len1][len2])

时间复杂度和空间复杂度

  • 时间复杂度: O(m * n),其中 m 和 n 分别是字符串 word1word2 的长度。这是因为需要遍历两个字符串的所有可能的字符组合来计算最长公共子序列的长度。
  • 空间复杂度: O(m * n),需要一个大小为 (m + 1) x (n + 1) 的动态规划数组 dp 来存储中间结果。

总结

这段代码通过计算最长公共子序列并利用其长度来间接计算编辑距离,提供了一种有效的解决方案。尽管这种方法并不是直接计算编辑距离的标准动态规划公式,但通过计算最长公共子序列的长度,然后根据这个信息推导出编辑距离,同样能高效地解决问题。这种方法在某些情况下可能更为直观,尤其是在理解最长公共子序列概念的基础上。如果需要进一步优化空间复杂度,可以考虑使用滚动数组技术,将空间复杂度降低到 O(min(m, n)),但这可能会使代码变得更复杂。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值