Studying-代码随想录训练营day44| 1143.最长公共子序列、1035.不相交的线、53.最大子序和、392.判断子序列

第44天,动态规划part11,子序列题型part02(ง •_•)ง💪,编程语言:C++

目录

1143.最长公共子序列

1035.不相交的线

53.最大子序和

392.判断子序列

总结


1143.最长公共子序列

文档讲解:代码随想录最长公共子序列

视频讲解:手撕最长公共子序列

题目:1143. 最长公共子序列 - 力扣(LeetCode)

学习:本题与最大重复子数组的不同在于,本题的序列不要求连续。类似于“最长上升子序列”和“最长连续递增序列”的区别。因此本题最大的不同就在于dp数组的设置以及递推公式。

从动归五部曲出发:

1.确定dp数组以及下标的含义:由于本题是子序列,不要求元素之间是连续的。因此本题可设置一个二维dp数组,dp[i][j]表示长度为[0, i - 1]的字符串text1与长度为[0, j - 1]的字符串text2的最长公共子序列为dp[i][j]。至于为什么是i - 1和 j - 1。其实是和上题一样,简化初始化步骤,否则需要对第一行和第一列单独进行初始化。

2.确定递推公式:主要有两个判断情况:text1[i - 1] 与 text2[j - 1]相同,text1[i - 1] 与 text2[j - 1]不相同。如果text1[i - 1] 与 text2[j - 1]相同,那么找到了一个公共元素,所以dp[i][j] = dp[i - 1][j - 1] + 1; 如果text1[i - 1] 与 text2[j - 1]不相同,那就看看dp[i - 1][j] 和 dp[i][j - 1]的情况,取最大值,这个地方其实是包含了两个字符串能否删减的思想,因为一个是比较text1[0, i - 2]与text2[0, j - 1],另一个是比较text1[0, i - 1]与text2[0, j - 2],相当于是每个字符串都考虑少一个元素的情况。这个思想在后面的题目中也有所体现。

3.dp数组初始化:由于我们设置的是i - 1和 j - 1,因此第一行和第二行设置为0即可,其余的情况会自行得到。

4.确定遍历顺序:由递推公式明显可以,有三个方向可以推出dp[i][j]。因此遍历顺序可以为从上到下,从左到右。

5.举例推导dp数组:

代码:

//时间复杂度O(n*m)
//空间复杂度O(n*m)
class Solution {
public:
    int longestCommonSubsequence(string text1, string text2) {
        if(text1.size() == 0 || text2.size() == 0) return 0;
        //动态规划  
        //1.确定dp数组以及下标的含义:
        //dp[i][j]长度为[0, i - 1]的字符串text1与长度为[0, j - 1]的字符串text2的最长公共子序列为dp[i][j]
        vector<vector<int>> dp(text1.size() + 1, vector<int> (text2.size() + 1, 0));
        //2.确定递推公式
        //判断text1[i - 1] 和 text2[j - 1]的两种可能
        //3.初始化dp数组:第一行和第一列为0
        //4.确定遍历顺序
        for(int i= 1; i <= text1.size(); i++) {
            for(int j = 1; j <= text2.size(); 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[text1.size()][text2.size()];
    }
};

1035.不相交的线

文档讲解:代码随想录不相交的线

视频讲解:手撕不相交的线

题目:1035. 不相交的线 - 力扣(LeetCode)

学习:本题实际上是上一题的一个应用,由于只有相等的数能够连线,且不能够出现相交的线。那么就可以认为是找到最长的公共子序列,且子序列之间顺序不能调换。这样和上一题就是一摸一样的了,因此本题的解法也和上一题如出一辙。求解的最大连线数,也就是求解最长的公共子序列和。

因此本题的代码和上一题相同:

代码:

//时间复杂度O(n*m)
//空间复杂度O(n*m)
class Solution {
public:
    int maxUncrossedLines(vector<int>& nums1, vector<int>& nums2) {
        //动态规划:1.确定dp数组以及下标的含义
        //dp[i][j]:表示nums1数组下标[0-(i - 1)]和nums2数组下标[0-(j-1)]内最大的公共子序列
        vector<vector<int>> dp(nums1.size() + 1, vector<int>(nums2.size() + 1, 0));
        //2.确定递推公式:同样试分析nums1[i - 1]和nums2[j - 1]的两种可能
        //3.初始化dp数组:由于我们使用了i-1和j-1下标,第一行和第一列初始化为0即可
        //4.确定遍历顺序
        int result = 0; //记录答案
        for(int i = 1; i <= nums1.size(); i++) {
            for(int j = 1; j <= nums2.size(); j++) {
                if(nums1[i - 1] == nums2[j - 1]) {
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                }
                else {
                    dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
                }
                if(result < dp[i][j]) {
                    result = dp[i][j];
                }
            }
        }
        return result;
    }
};

53.最大子序和

文档讲解:代码随想录最大子序和

视频讲解:手撕最大子序和

题目:53. 最大子数组和 - 力扣(LeetCode)

学习:本题有两种解题方法。第一种是使用贪心算法,我们只需要保证每次进行加减的是正数即可,因为正数才会对后续的加减产生有利的影响,这在我们贪心算法章节写写过:

代码:贪心算法

//时间复杂度O(n)
//空间复杂度O(1)
class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int count = 0; //记录当前和
        int maxcount = INT_MIN; //记录最大值
        for(int i = 0; i < nums.size(); i++) {
            //如果当前和小于0,抛弃前和,改为当前值
            if(count < 0) {
                count = nums[i];
            }
            else {
                count += nums[i];
            }
            //如果当前和大于最大值,更新最大值
            if (count > maxcount) {
                maxcount = count;
            }
        }
        return maxcount;
    }
};

第二种方法则是采用动态规划的方式,从动归五部曲出发进行分析。

1.确定dp数组以及下标的含义:首先我们可以设置一个以为dp数组,dp[i]我们肯定是想要让它能够表示[0-i]区间内的最大和,这样dp[nums.size() - 1]就是要我们求解的值。但本题却不可以这么设置,因为本题要求的是子数组,是连续的元素。也就意味着实际上本题和“最大重复子数组”是一样的。我们确定nums[i]的时候,就已经对区间有限制了,必须要包含nums[i]。因此本题dp[i]表示包括下标i(以nums[i]为结尾)的最大连续子序列和为dp[i]

2.确定递推公式:根据设置的含义,可以推出,dp[i]只能从两个方向推出:dp[i - 1] + nums[i],即:nums[i]加入当前连续子序列和;nums[i],即:从头开始计算当前连续子序列和。这两种可能,因为我们是不能够跨元素来取值的,因此只能借前面的值或者就只要当前的值。

3.初始化dp数组:显然dp[0] = nums[0],表示第一个元素的值。

4.确定遍历顺序:递推公式中dp[i]依赖于dp[i - 1]的状态,需要从前向后遍历。

5.举例推导dp数组:

代码:

//时间复杂度O(n)
//空间复杂度O(n)
class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        if(nums.size() == 1) return nums[0];
        //动态规划
        //1.确定dp数组以及下标的含义
        vector<int> dp(nums.size(), 0); //dp[i]表示以下标i结尾的连续子数组的最大和
        //2.确定递推公式
        //dp[i] = max(nums[i], dp[i - 1] + nums[i]);
        //3.初始化dp数组
        dp[0] = nums[0];
        int result = dp[0]; //保存答案
        //4.确定遍历顺序
        for(int i = 1; i < nums.size(); i++) {
            dp[i] = max(nums[i], dp[i - 1] + nums[i]);
            if(result < dp[i]) {
                result = dp[i];
            }
        }
        return result;
    }

392.判断子序列

文档讲解:代码随想录判断子序列

视频讲解:手撕判断子序列

题目: 392. 判断子序列 - 力扣(LeetCode)

学习:本题又是找子序列,稍微不同的是,本题需要我们判断s是否为t的子序列。其实也可以理解为t和s的最长公共子序列就是s,换句话说,只要t和s的最长公共子序列的长度是s.size(),那么就说明s是t的子序列,因为最长就只能达到s.size()这么长了。

由此本题的解法和最长公共子序列是一样的,只有一处可以进行部分修改,也就是递推公式,由于本题是判断是否是子序列,不需要我们真的找到最长子序列的长度,因此本题可以认为字符串s的各个字符是必须要包含的,因此当s[i - 1] 和 t[i - 1]不等时,可直接dp[i][j] = dp[i][j - 1],不用管s[i-1]的情况,因为字符串s是不能够减少的。(当然不改也不会有错误)

代码:

//时间复杂度O(n*m)
//空间复杂度O(n*m)
class Solution {
public:
    bool isSubsequence(string s, string t) {
        //1.确定dp数组以及下标的含义
        vector<vector<int>> dp(s.size() + 1, vector<int>(t.size() + 1, 0));
        
        for(int i = 1; i <= s.size(); i++) {
            for(int j = 1; j <= t.size(); j++) {
                if(s[i - 1] == t[j - 1]) {
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                }
                else {
                    dp[i][j] = dp[i][j - 1]; //不同点,因为s字符串每个字符都必须考虑
                }
            }
        }
        return dp[s.size()][t.size()] == s.size();
    }
};

本题还有时间复杂度更低的解法,直观的一种就是可以采用双指针的解法,因为本题只需要判断一个字符串在另一个字符串中是否出现。

//时间复杂度O(n + m)
//空间复杂度O(1)
class Solution {
public:
    bool isSubsequence(string s, string t) {
        int n = s.length(), m = t.length();
        int i = 0, j = 0;
        while (i < n && j < m) {
            if (s[i] == t[j]) {
                i++;
            }
            j++;
        }
        return i == n;
    }
};

总结

子序列题型,需要十分注意dp数组的设置,是以i - 1为结尾的数组,还是[0-i-1]区间内的数组。取决于是否最后一个元素加入是有前置要求的。

假如最后一个元素是可以直接加入的,例如最大公共子序列,不相交的线等,那么就可以是[0-i- 1]的区间。

但加入最后一个元素加入的话,必须它前面没有元素,或者必须有它前面相邻的元素, 那么就不能是区间了,而需要时以i - 1为结尾。

还需要多加练习,记忆。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值