算法日记day 43(动归之不同的子序列|两个字符串的删除操作)

一、不同的子序列

题目:

给你两个字符串 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" 的方案。 
babgbag
babgbag
babgbag
babgbag
babgbag

思路:

本题dp数组的含义是以i-1为结尾的字符串s和以j-1为结尾的字符串t的可构成t的情况有dp[i][j]种,如果有相对应的元素,即为s[i-1]=t[i-1],此时应有两种情况

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

前面的dp[i-1][j-1]是元素都匹配的情况,后面的dp[i-1][j]是模拟删除一种元素,即不匹配时计算s的前i-1个元素和 t 前 j 个元素

例如  s="bagg"

          t="bag" 例子中存在有s[2] = t[2],但是也存在s[3] = t[2],两种均需考虑

相对应的如果不存在对应元素的匹配,即s[i-1]不等于t[i-1],此时只能从上一层继承,即为

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

代码:

public int numDistinct(String s, String t) {
    // 计算字符串 s 和 t 的长度,并在它们的长度上各加 1
    int n = s.length() + 1;
    int m = t.length() + 1;

    // 将字符串 s 和 t 转换为字符数组,以便在后续操作中更高效地访问单个字符
    char[] s1 = s.toCharArray();
    char[] t1 = t.toCharArray();

    // 创建一个二维数组 dp,用于存储子问题的解
    // dp[i][j] 表示 s 的前 i 个字符中,t 的前 j 个字符出现的子序列的数量
    int[][] dp = new int[n][m];

    // 初始化 dp 表的第一列,表示 t 是空字符串时的情况
    // 任何 s 的前 i 个字符中,空字符串作为子序列的数量总是 1
    for (int i = 0; i < s.length() + 1; i++) {
        dp[i][0] = 1;
    }

    // 填充 dp 表的其他部分
    for (int i = 1; i < n; i++) {
        for (int j = 1; j < m; j++) {
            if (s1[i - 1] == t1[j - 1]) {
                // 当前字符匹配
                // dp[i][j] 是由两个部分组成:
                // 1. dp[i - 1][j - 1]:当前字符匹配,计算 s 的前 i-1 个字符和 t 的前 j-1 个字符
                // 2. dp[i - 1][j]:当前字符不匹配,计算 s 的前 i-1 个字符和 t 的前 j 个字符
                dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
            } else {
                // 当前字符不匹配
                // dp[i][j] 只能从 dp[i - 1][j] 继承,即当前字符在 s 中不考虑
                dp[i][j] = dp[i - 1][j];
            }
        }
    }

    // 返回 dp 表的右下角的值,即整个字符串 s 中 t 作为子序列的数量
    return dp[s.length()][t.length()];
}

 

二、两个字符串的删除操作

题目:

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

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

示例 1:

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

示例  2:

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

思路:

dp数组的含义为以i-1为结尾的字符串word1和以j-1为结尾的字符串word2使其两字符串相等的操作需要dp[i][j]步,共有三种情况

第一种:删除字符,删除其中的一个元素,操作数加一

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

第二种:添加字符,加入一个字符,操作数加一 

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

第三种:替换字符,相当于先删除再加入,操作数加二

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

在初始化dp数组时,由于其含义,对边界初始化分别应是

                                                   dp[i][0] = i,dp[0][j] = j 

代码:

public int minDistance(String word1, String word2) {
    // 创建 dp 表,dp[i][j] 代表将 word1 的前 i 个字符转换为 word2 的前 j 个字符的最小编辑距离
    int[][] dp = new int[word1.length() + 1][word2.length() + 1];

    // 初始化第一列,表示将 word1 的前 i 个字符变为空字符串所需的删除操作数
    for (int i = 0; i < word1.length() + 1; i++)
        dp[i][0] = i;

    // 初始化第一行,表示将空字符串变为 word2 的前 j 个字符所需的插入操作数
    for (int j = 0; j < word2.length() + 1; j++)
        dp[0][j] = j;

    // 填充 dp 表,计算最小编辑距离
    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 - 1][j - 1] + 2
                // 删除字符:dp[i - 1][j] + 1
                // 插入字符:dp[i][j - 1] + 1
                dp[i][j] = Math.min(dp[i - 1][j - 1] + 2, Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1));
            }
        }
    }

    // 返回 dp 表中最后一个元素的值,即 word1 转换为 word2 的最小编辑距离
    return dp[word1.length()][word2.length()];
}

 另一种思路:

由于最后求得的字符串,相当于是求得了两个字符的最大公共子序列,只需要在最大公共子序列的思路上返回初始的两数组长度之和减去最长公共子序列长度的两倍,即为需要操作的次数

代码:

public int minDistance(String word1, String word2) {
    int len1 = word1.length();  // 获取 word1 的长度
    int len2 = word2.length();  // 获取 word2 的长度

    // 创建一个二维数组 dp,其中 dp[i][j] 表示 word1 的前 i 个字符和 word2 的前 j 个字符的最长公共子序列的长度
    int[][] dp = new int[len1 + 1][len2 + 1];

    // 填充 dp 表
    for (int i = 1; i <= len1; i++) {
        for (int j = 1; j <= len2; j++) {
            // 如果当前字符相同,最长公共子序列的长度增加 1
            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]);
            }
        }
    }

    // dp[len1][len2] 是 word1 和 word2 的最长公共子序列的长度
    // 编辑距离等于 word1 和 word2 的总长度减去 2 倍的最长公共子序列的长度
    // 这个公式的意思是:从两个字符串中删除掉不在最长公共子序列中的字符,得到的字符数之和就是最小编辑距离
    return len1 + len2 - dp[len1][len2] * 2;
}

今天的学习就到这里 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值