记录动态规划(二)

总结今日动态规划

1143. 最长公共子序列力扣

给定两个字符串 text1 和 text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0 。

一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。

例如,"ace" 是 "abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。
两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。

示例 1:

输入:text1 = "abcde", text2 = "ace" 
输出:3  
解释:最长公共子序列是 "ace" ,它的长度为 3 。
示例 2:

输入:text1 = "abc", text2 = "abc"
输出:3
解释:最长公共子序列是 "abc" ,它的长度为 3 。 

直接说结论的思考过程。其实可以这样想,假如每次我们都取到了最后一个字母了(实际上不一定是),那么就有四种可能,都选,或者二选一,再或者都不选,那么我们来定义一个二维数组,dp[i][j]代表0 -- i 和0 -- j 中最大有多少个公共子序列,然后根据上述结论

1.两个一样,那么都可以选

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

2.不一样,那么就得讨论

dp[i][j] = max(max(dp[i - 1][j], dp[i][j - 1]), dp[i - 1][j - 1])

其实这里我们可以证明的到dp[i - 1][j - 1] 一定是比前面那两个小的,所以我们直接简化

dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])

这里还要提一嘴初始化的问题

dp[i][0] = 0 dp[0][j] = 0 这两个式子应该很好理解,就是代表只有一个字符串,另外一个字符串是空的,所以公共子序列一定是0

然后再来看代码怎么写

        //二维数组

        int n = text1.size(), m = text2.size();
        vector<vector<int>> dp(n + 1, vector<int>(m + 1, 0));
        for(int i = 1; i <= n; ++i)
            for(int j = 1; j <= m; ++j)
            {
                if(text1[i - 1] == text2[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1;
                else dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
            }
        return dp[n][m];


        // 一维数组
        
        int n = text1.size(), m = text2.size();
        vector<int> dp(m + 1, 0);
        for(int i = 1; i <= n; ++i)
        {
            int pre = dp[0];     // 左上角元素,dp[i - 1][j - 1];
            for(int j = 1; j <= m; ++j)
            {
                int temp = dp[j];
                if(text1[i - 1] == text2[j - 1]) dp[j] = pre + 1;
                else dp[j] = max(dp[j], dp[j - 1]);
                pre = temp;
            }
        }
        return dp[m];

这里提一嘴如何变成一维数组,因为我们只涉及到了左边dp[i][j - 1],上边dp[i - 1][j],左上dp[i - 1][j - 1],所以每次我们更新数据的时候,只有左上会被覆盖,那么就需要我们来一个临时变量去储存左上,也就是代码中的pre,然后每次在j的循环里记录左上。如果写的不清楚,看不懂,建议看灵神的视频最长公共子序列 编辑距离【基础算法精讲 19】_哔哩哔哩_bilibili

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

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

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

示例 1:

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

示例  2:

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

这个可以说跟上面那个一模一样,只不过你得想的到这个点,要让他们两个字符串经过一系列操作相等,那么其实就是求公共子序列的最大长度,然后两个原本的长度相加减去2倍的公共长度就可以得到答案。

class Solution 
{
public:
    int minDistance(string text1, string text2) 
    {
        int n = text1.size(), m = text2.size();
        vector<int> dp(m + 1, 0);
        for(int i = 1; i <= n; ++i)
        {
            int pre = dp[0];     // 左上角元素,dp[i - 1][j - 1];
            for(int j = 1; j <= m; ++j)
            {
                int temp = dp[j];
                if(text1[i - 1] == text2[j - 1]) dp[j] = pre + 1;
                else dp[j] = max(dp[j], dp[j - 1]);
                pre = temp;
            }
        }
        return n + m - 2 * dp[m];
    }
};

 72. 编辑距离力扣

给你两个单词 word1 和 word2, 请返回将 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')

 其实这个题也是一样的思路,只不过都得能变式到这一步,首先是删除操作,那么不就是dp[i - 1][j],相当于把dp[i][j]变成了它,然后插入就相当于dp[i][j - 1],因为你插入了之后两个字符串的最后一个字母就相等了,那么其实就是j删除一个,然后就是替换,两个都一样了,然后都得没,所以是dp[i - 1][j - 1],然后来看一下代码怎么写。

        int n = word1.size(), m = word2.size();
        if(n == 0) return m;
        if(m == 0) return n;
        vector<vector<int>> dp(n + 1, vector<int>(m + 1, 0));
        // 这个题很相似1143
        // 其实插入就是dp[i][j - 1],删除就是dp[i - 1][j],替换就是dp[i - 1][j - 1]
        for(int i = 1; i <= n; ++i)
        {
            dp[i][0] = i;
            for(int j = 1; j <= m; ++j)
            {
                dp[0][j] = j;
                if(word1[i - 1] == word2[j - 1]) dp[i][j] = dp[i - 1][j - 1];
                else dp[i][j] = min(min(dp[i - 1][j], dp[i][j - 1]), 
                         dp[i - 1][j - 1]) + 1;
            }
        }
        return dp[n][m];

    
    // 还可以改成一维数组,跟1143一样
    int minDistance(string word1, string word2) 
    {
        int n = word1.size(), m = word2.size();
        if(n == 0) return m;
        if(m == 0) return n;
        vector<int> dp(m + 1, 0);
        for(int j = 1; j <= m; ++j) dp[j] = j;
        for(int i = 1; i <= n; ++i)
        {
            int pre = dp[0];
            // 初始化第一列
            ++dp[0];
            for(int j = 1; j <= m; ++j)
            {
                int temp = dp[j];
                if(word1[i - 1] == word2[j - 1]) dp[j] = pre;
                else dp[j] = min(min(dp[j - 1], dp[j]), pre) + 1;
                pre = temp;
            }
        }
        return dp[m];
    }

 值得一提的是,这里的初始化和前面两个不一样,因为这里看的是操作数,所以

dp[i][0] = i  dp[0][j] = j 因为你从一个字符串变成一个空的,那么肯定要操作你的长度次数才能成功。  

 97. 交错字符串力扣

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

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

s = s1 + s2 + ... + sn
t = t1 + t2 + ... + tm
|n - m| <= 1
交错 是 s1 + t1 + s2 + t2 + s3 + t3 + ... 或者 t1 + s1 + t2 + s2 + t3 + s3 + ...
注意:a + b 意味着字符串 a 和 b 连接。

示例 1:



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

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

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

首先这个题官解说不能用双指针,其实是可以的,因为我们正常使用双指针的时候会出现一个错误,那就是一但我们两个都可以匹配的时候不知道该选哪一个,那么我们可以在双指针的基础上写一个记忆化搜索,那么就可以完美的避开选择的问题,这里我就不给出代码了,因为主要是写动态规划的代码。 这里我看别人用了路径方法,其原理就是两个字符串构成一个二维数组,然后每一次都只能往右和往下走力扣,然后我们定义一个二维数组,dp[i][j]这里代表前i个字符和前j个字符可以构成目标i + j 的部分吗。那么我们每一次都只要比较最后一个字,直接看代码。

class Solution 
{
public:
    // 这个题得画个图就清楚了
    // 二维数组求路径是否合格
    // https://leetcode.cn/problems/interleaving-string/solutions/335561/lei-si-lu-jing-wen-ti-zhao-zhun-zhuang-tai-fang-ch/
    bool isInterleave(string s1, string s2, string s3) 
    {
        auto f = vector < vector <int> > (s1.size() + 1, vector <int> (s2.size() + 1, false));

        int n = s1.size(), m = s2.size(), t = s3.size();

        if (n + m != t) 
        {
            return false;
        }

        f[0][0] = true;
        for (int i = 0; i <= n; ++i) 
        {
            for (int j = 0; j <= m; ++j) 
            {
                int p = i + j - 1;
                if (i > 0) 
                {
                    f[i][j] |= (f[i - 1][j] && s1[i - 1] == s3[p]);
                }
                if (j > 0) 
                {
                    f[i][j] |= (f[i][j - 1] && s2[j - 1] == s3[p]);
                }
            }
        }

        return f[n][m];
    }
};

 1137. 第 N 个泰波那契数力扣

简单题,就不解释了。

总结,这一次的动态规划其实跟记忆化搜索很像,都是选与不选,然后由上一个状态转移过来,说实话跟背包问题很像,等有时间把背包问题也写一篇博客来介绍。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值