代码随想录算法训练营第 44 天 | 115. 不同的子序列、583. 两个字符串的删除操作、72. 编辑距离

115. 不同的子序列

题目链接

dp[i][j]:考虑 i - 1 的 s 包含的考虑 j - 1 的 t 的个数。

递推公式:
dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j]
dp[i][j] = dp[i - 1][j]

s 遍历到了 babga,t 是 bag
假设 s 的下一个字符是 g
那么可能是 babgag 里找 bag,也可能是 babga 里找 ba

初始化:第一列全为 1

class Solution {
    public int numDistinct(String s, String t) {
        int n1 = s.length();
        int n2 = t.length();
        int[][] dp = new int[n1 + 1][n2 + 1]; // dp[i][j]:考虑 i - 1 的 s 包含考虑 j - 1 的t 的个数。

        for (int i = 0; i <= n1; i++) { // 初始化:第一列全为 1
            dp[i][0] = 1;
        }

        for (int i = 1; i <= n1; i++) {
            for (int j = 1; j <= n2; 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[n1][n2];
    }
}

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

题目链接

方法一:正常考虑

dp[i][j]:以 i - 1、j - 1 结尾的两个字符串要相同的最小操作次数。

递推公式:
dp[i][j] = dp[i - 1][j - 1]
dp[i][j] = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1)

初始化:第一行和第一列都初始化为对应考虑的字符串长度。

class Solution {
    public int minDistance(String word1, String word2) {
        int n1 = word1.length();
        int n2 = word2.length();

        int[][] dp = new int[n1 + 1][n2 + 1]; // dp[i][j]:以 i - 1、j - 1 结尾的两个字符串要相同的最小操作次数。
        
        for (int i = 0; i <= n1; i++) { // 初始化:删字符串个长度才能变成空串
            dp[i][0] = i;
        }
        for (int j = 0; j <= n2; j++) {
            dp[0][j] = j;
        }

        for (int i = 1; i <= n1; i++) {
            for (int j = 1; j <= n2; 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[n1][n2];
    }
}

方法二:转换成最长公共子序列

class Solution {
    public int minDistance(String word1, String word2) {
        int n1 = word1.length();
        int n2 = word2.length();
        int[][] dp = new int[n1 + 1][n2 + 1];

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

72. 编辑距离

题目链接

dp[i][j]:以 i - 1、j - 1 结尾的最小编辑次数为 dp[i][j]。

递推公式:
dp[i][j] = dp[i - 1][j - 1]

dp[i][j] = Math.min(Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1), dp[i - 1][j - 1] + 1)
(前两个为删除,后一个为替换。(新增和删除等价。))

初始化:初始化为考虑的字符串长度。

class Solution {
    public int minDistance(String word1, String word2) {
        int n1 = word1.length();
        int n2 = word2.length();

        int[][] dp = new int[n1 + 1][n2 + 1]; // dp[i][j]:以 i - 1、j - 1 结尾的最小编辑次数为 dp[i][j]。

        for (int i = 0; i <= n1; i++) { // 初始化:初始化为考虑的字符串长度。
            dp[i][0] = i;
        }
        for (int j = 0; j <= n2; j++) {
            dp[0][j] = j;
        }

        for (int i = 1; i <= n1; i++) {
            for (int j = 1; j <= n2; 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][j - 1] + 1), dp[i - 1][j - 1] + 1); // 前两个为删除,后一个为替换。(新增和删除等价。)
                }
            }
        }

        return dp[n1][n2];
    }
}

优化:对特殊情况的巧妙判断

        // 有一个字符串为空串
        if (n1 * n2 == 0) {
            return n1 + n2;
        }

子序列问题总结

  1. 评论区笔记
  2. 评论区笔记2

一般如果要是求的子数组是连续的,或者即使不是连续的,要求单调这种,都是需要定义 dp 是以 nums[i] 为结尾,最终结果是 max(dp)。因为如果不定义是以 nums[i] 为结尾,那么这时候即使做 nums[i] 的比较,也无法更新 dp[i],因为不知道 dp[i-1] 是不是以 nums[i-1] 结尾,如果不是,题目要求的数组的连续的,那就没法 dp[i] = dp[i-1]+1。又或者类似递增子序列,我们需要 dp[i-1] 的最后一位的具体取值是什么,才好和当前的 nums[i] 做判断。
关键点在于,这道题我们需不需要记录住 dp[i] 对应的状态的最后一个取值,例如最长递增子序列,最长重复子数组,我们是需要记住的。此时需要定义 dp[i] 为以 nums[i] 为结尾的。而像最长公共子序列等,我们不需要知道 dp[i-1] 对应的最长公共子序列的最后一位具体的 index 或者 value,这个时候就不需要要求 dp[i] 是以 nums[i] 为结尾的,只要定义在 [0, i] 这个区间内就可以。

第二十二算法训练营主要涵盖了Leetcode题目中的三道题目,分别是Leetcode 28 "Find the Index of the First Occurrence in a String",Leetcode 977 "有序数组的平方",和Leetcode 209 "长度最小的子数组"。 首先是Leetcode 28题,题目要求在给定的字符串中找到第一个出现的字符的索引。思路是使用双指针来遍历字符串,一个指向字符串的开头,另一个指向字符串的结尾。通过比较两个指针所指向的字符是否相等来判断是否找到了第一个出现的字符。具体实现的代码如下: ```python def findIndex(self, s: str) -> int: left = 0 right = len(s) - 1 while left <= right: if s[left == s[right]: return left left += 1 right -= 1 return -1 ``` 接下来是Leetcode 977题,题目要求对给定的有序数组中的元素进行平方,并按照非递减的顺序返回结果。这里由于数组已经是有序的,所以可以使用双指针的方法来解决问题。一个指针指向数组的开头,另一个指针指向数组的末尾。通过比较两个指针所指向的元素的绝对值的大小来确定哪个元素的平方应该放在结果数组的末尾。具体实现的代码如下: ```python def sortedSquares(self, nums: List[int]) -> List[int]: left = 0 right = len(nums) - 1 ans = [] while left <= right: if abs(nums[left]) >= abs(nums[right]): ans.append(nums[left ** 2) left += 1 else: ans.append(nums[right ** 2) right -= 1 return ans[::-1] ``` 最后是Leetcode 209题,题目要求在给定的数组中找到长度最小的子数组,
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值