【七十四】【算法分析与设计】115. 不同的子序列,72. 编辑距离,97. 交错字符串,记忆化递归自顶向下的动态规划

115. 不同的子序列

  • 给你两个字符串 s t ,统计并返回在 s子序列t 出现的个数,结果需要对 10(9) 7 取模。

示例 1:

 
 

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

示例 2:

 
 

输入:s = "babgbag", t = "bag" 输出:5 解释: 如下所示, 有 5 种可以从 s 中得到 "bag" 的方案。 babgbagbabgbagbabgbagbabgbagbabgbag

提示:

  • 1 <= s.length, t.length <= 1000

  • st 由英文字母组成

1.

动态规划定义,区间dp.

s中[0,i]区间子序列t[0,j]区间子串出现的个数.

如果i位置字符匹配j位置字符,等价于[0,i-1]区间子序列匹配[0,j-1]子串的个数.

如果i位置字符不匹配j位置字符,等价于[0,i-1]区间子序列匹配[0,j]子串个数.

 
class Solution {
public:
    string s, t; // 定义两个字符串s和t,分别代表源字符串和目标字符串
    int n, m;    // n和m分别代表s和t的长度
    vector<vector<int>> dp; // 定义一个二维动态规划数组dp
    const int MOD = 1e9 + 7; // 定义一个常数MOD,用于在计算过程中取模

    // 初始化函数,用于在每次计算前重置dp数组和s、t字符串
    void solveinit() {
        n = s.size(), m = t.size();
        dp.clear(), dp.resize(n, vector<int>(m, -1)); // 清除dp数组,并根据s和t的长度重新初始化
    }

    // 深度优先搜索函数,用于计算动态规划的值
    int dfs(int i, int j) {
        if (i < j) { // 如果i小于j,说明s的当前索引小于t的当前索引,无法形成t,返回0
            if (i >= 0 && j >= 0)
                dp[i][j] = 0;
            return 0;
        }
        if (j == -1) { // 如果j为-1,说明t已经遍历完,返回1表示有一个匹配的空子序列
            return 1;
        }

        // 如果dp[i][j]已经被计算过,则直接返回其值
        if (dp[i][j] != -1)
            return dp[i][j];

        // 如果s[i]等于t[j],则有两种情况可以形成t[j]:
        // 1. 从s[i-1]和t[j-1]继续形成t[j](即s中去掉当前字符)
        // 2. 从s[i-1]和t[j]继续形成t[j](即s中保留当前字符)
        if (s[i] == t[j]) {
            dp[i][j] = (dfs(i - 1, j - 1) + dfs(i - 1, j)) % MOD; // 计算两种情况的和,并取模
        } else {
            // 如果s[i]不等于t[j],则只有一种情况可以继续形成t[j],即从s[i-1]和t[j]继续
            dp[i][j] = dfs(i - 1, j) % MOD;
        }
        return dp[i][j] % MOD; // 返回计算结果,并取模
    }

    // 主函数,用于计算s中形成t的不同方式的数量
    int numDistinct(string _s, string _t) {
        s = _s; // 将传入的字符串赋值给s和t
        t = _t;
        solveinit(); // 初始化操作
        return dfs(n - 1, m - 1) % MOD; // 从s的末尾和t的末尾开始动态规划,并返回结果
    }
};

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')

提示:

  • 0 <= word1.length, word2.length <= 500

  • word1word2 由小写英文字母组成

 
// #define int long long
// #undef int
// #define endl '\n'
// #define p pair<long long, long long>
class Solution {
public:
    string word1, word2; // 定义两个单词 word1 和 word2
    int n,m; // 分别记录两个单词的长度
    vector<vector<int>> memory; // 定义一个二维vector用于记忆化搜索
    // 初始化函数,用于初始化单词长度和记忆数组
    void solveinit() {
        n = word1.size(), m = word2.size(); // 获取单词长度
        memory.clear(); // 清空记忆数组
        memory.resize(n, vector<int>(m, -1)); // 初始化记忆数组,全部赋值为-1
    }
    // dfs搜索函数,返回word1转换成word2所需的最少操作数
    int dfs(int i, int j) {
        if (i == -1) // 当 word1 遍历完毕
            return j + 1; // 返回剩余 word2 的长度,即插入操作数
        if (j == -1) // 当 word2 遍历完毕
            return i + 1; // 返回剩余 word1 的长度,即删除操作数
        if(memory[i][j] != -1) return memory[i][j]; // 如果已经计算过该状态,直接返回记忆数组中的结果
        if (word1[i] == word2[j]) // 当两个位置字符相等时,无需操作
            return dfs(i - 1, j - 1); // 继续比较下一位
        // 分别计算插入、替换、删除三种操作的操作数
        int nums1 = dfs(i, j - 1) + 1; // 插入操作
        int nums2 = dfs(i - 1, j - 1) + 1; // 替换操作
        int nums3 = dfs(i - 1, j) + 1; // 删除操作
        memory[i][j] = min(nums1, min(nums2, nums3)); // 记录当前状态下的最小操作数
        return memory[i][j]; // 返回最小操作数
    }
    // 主函数,计算最小操作数
    int minDistance(string _word1, string _word2) {
        word1 = _word1, word2 = _word2; // 初始化单词
        solveinit(); // 执行初始化函数
        return dfs(word1.size() - 1, word2.size() - 1); // 返回计算结果
    }
};

97. 交错字符串

给定三个字符串 s1s2s3,请你帮忙验证 s3 是否是由 s1s2 交错 组成的。

两个字符串 st 交错 的定义与过程如下,其中每个字符串都会被分割成若干 非空

子字符串

  • s = s(1)s(2)... + s(n)

  • t = t(1)t(2)... + t(m)

  • |n - m| <= 1

  • 交错s(1)t(1)s(2)t(2)s(3)t(3)... 或者 t(1)s(1)t(2)s(2)t(3)s(3)...

注意:a + b 意味着字符串 ab 连接。

示例 1:

输入:s1 = "aabcc", s2 = "dbbca", s3 = "aadbbcbcac" 输出:true

示例 2:

输入:s1 = "aabcc", s2 = "dbbca", s3 = "aadbbbaccc" 输出:false

示例 3:

输入:s1 = "", s2 = "", s3 = "" 输出:true

提示:

  • 0 <= s1.length, s2.length <= 100

  • 0 <= s3.length <= 200

  • s1s2、和 s3 都由小写英文字母组成

进阶:您能否仅使用 O(s2.length) 额外的内存空间来解决它?

1.

注意判断的时候不要数组越界了.

边界的处理不要越界.

 
class Solution {
public:
    string s1, s2, s3; // 定义三个字符串s1、s2和s3
    int n1, n2, n3;    // n1、n2和n3分别代表s1、s2和s3的长度
    vector<vector<int>> dp; // 定义一个二维动态规划数组dp,用于存储子问题的解

    // 初始化函数,用于在每次计算前重置dp数组和s1、s2、s3字符串
    void solveinit() {
        n1 = s1.size(), n2 = s2.size(), n3 = s3.size();
        dp.clear(), dp.resize(n1, vector<int>(n2, -1)); // 清除dp数组,并根据s1和s2的长度重新初始化
    }

    // 深度优先搜索函数,用于计算动态规划的值
    int dfs(int i, int j) {
        // 如果s1和s2都遍历完,检查s3是否也遍历完
        if (i == -1 && j == -1)
            return n3 == 0 ? true : false;

        // 如果s1遍历完,检查剩余的s2是否能与s3的剩余部分匹配
        if (i == -1) {
            if (s2.substr(0, j + 1) == s3.substr(i + j + 1, j + 1))
                return true;
            else
                return false;
        }
        // 如果s2遍历完,检查剩余的s1是否能与s3的剩余部分匹配
        if (j == -1) {
            if (s1.substr(0, i + 1) == s3.substr(j + i + 1, i + 1))
                return true;
            else
                return false;
        }

        // 如果当前状态已经被计算过,则直接返回其值
        if (dp[i][j] != -1)
            return dp[i][j];

        // 如果s1[i]和s2[j]都能与s3[i+j+1]匹配,则有两种走法:从s1去掉当前字符继续,或从s2去掉当前字符继续
        if (i + j + 1 < n3 && s1[i] == s3[i + j + 1] && s2[j] == s3[i + j + 1]) {
            dp[i][j] = dfs(i - 1, j) | dfs(i, j - 1); // 两种走法的逻辑或
        }
        // 如果只有s2[j]能与s3[i+j+1]匹配,则只能从s2去掉当前字符继续
        else if (i + j + 1 < n3 && s2[j] == s3[i + j + 1]) {
            dp[i][j] = dfs(i, j - 1);
        }
        // 如果只有s1[i]能与s3[i+j+1]匹配,则只能从s1去掉当前字符继续
        else if (i + j + 1 < n3 && s1[i] == s3[i + j + 1]) {
            dp[i][j] = dfs(i - 1, j);
        } else {
            // 如果s1[i]和s2[j]都不能与s3[i+j+1]匹配,则当前状态为false
            dp[i][j] = false;
        }

        return dp[i][j]; // 返回计算结果
    }

    // 主函数,用于验证s3是否由s1和s2交错组成
    bool isInterleave(string _s1, string _s2, string _s3) {
        s1 = _s1, s2 = _s2, s3 = _s3; // 将传入的字符串赋值给s1、s2和s3
        solveinit(); // 初始化操作
        // 如果s1和s2的总长度小于s3的长度,则s3不可能由s1和s2交错组成
        if (n1 + n2 < n3)
            return false;
        // 对s1和s2的所有前缀进行dfs搜索,填充dp数组
        for (int i = 0; i < n1; i++) {
            for (int j = 0; j < n2; j++) {
                dfs(i, j);
            }
        }
        // 从s1和s2的末尾开始验证,是否能形成s3
        return dfs(n1 - 1, n2 - 1);
    }
};

结尾

最后,感谢您阅读我的文章,希望这些内容能够对您有所启发和帮助。如果您有任何问题或想要分享您的观点,请随时在评论区留言。

同时,不要忘记订阅我的博客以获取更多有趣的内容。在未来的文章中,我将继续探讨这个话题的不同方面,为您呈现更多深度和见解。

谢谢您的支持,期待与您在下一篇文章中再次相遇!

  • 7
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

妖精七七_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值