C++刷题笔记(41)——子序列、leetcode300、674、718、1143、1035、53

题目1:300.最长上升子序列

在这里插入图片描述
1.确定dp数组以及下标的含义
dp[i]表示 从[0,i]中 以nums[i]结尾的最长上升子序列 的 长度

2.确定状态转移方程
如果一个上升子序列后接了一个较大数,那么就能构成一个更长的上升子序列。
因此如果nums[i]>nums[j] (j<i),且有dp[j] (dp[j]为[0,i-1]的最长上升子序列)的值,那么[0,i]的最长升序子序列 等于 j从0到i-1各个位置的最长升序子序列 + 1 的最大值。

状态转移方程为:dp[i] = max(dp[i],dp[j]+1)

3.dp数组初始化
dp[i] = 1,即一个字符就是长度为1的上升子序列

4.确定遍历顺序
从前向后遍历

5.举例推导dp数组
在这里插入图片描述

class Solution {
public:
    int lengthofLIS(vector<int>& nums) {
        if (nums.size() <= 1) return nums.size();
        vector<int> dp(nums.size(), 1);
        int result = 0;
        for (int i = 0; i < nums.size(); i++) {  //i也可以直接从1开始,因为j<i,就算i从0开始也会直接跳到i++
            for (int j = 0; j < i; j++) {
                if (nums[i] > nums[j]) dp[i] = max(dp[i], dp[i] + 1);
            }
            if (dp[i] > result) result = dp[i];  //这里就是找dp[i]数组中的最大值
        }
        return result;//return *max_element(dp.begin(), dp.end());
    }
};

仔细推敲代码就会发现其实是有双指针思想的

题目2:674.最长连续递增序列

在这里插入图片描述
这一题和300相比多了个连续,即求不被其他数字隔开的最长递增子序列

1.确定dp数组以及下标的含义
dp[i]表示 数组[0,i]中的 最长连续上升子序列 的 长度

2.确定状态转移方程
如果 nums[i + 1] > nums[i],那么以 i+1 为结尾的数组的连续递增的子序列长度 一定等于 以i为结尾的数组的连续递增的子序列长度 + 1
dp[i + 1] = dp[i] + 1

这里除了状态转移方程和300不一样之外,本题只需要用一层for循环遍历,因为比较的是连续的两个数nums[i+1]和nums[i]

3.dp数组初始化
dp[i] = 1,即一个字符就是长度为1的上升子序列

4.确定遍历顺序
从前向后遍历

5.举例推导dp数组
在这里插入图片描述

class Solution {
public:
    int findLengthOfLCIS(vector<int>& nums) {
        if (nums.size() == 0) return 0;
        int result = 1;
        vector<int> dp(nums.size(), 1);
        for (int i = 0; i < nums.size() - 1; i++) {
            if (nums[i + 1] > nums[i]) dp[i + 1] = dp[i] + 1;
            else if (dp[i + 1] > result) result = dp[i + 1];
        }
        return result;
    }
};

题目3:718.最长重复子数组

在这里插入图片描述
感觉这道题代码随想录的解题思路有些牵强了,有需要的可以看评论区大佬的题解:「手画图解」动态规划 思路解析 | 718 最长重复子数组

1.确定dp数组以及下标的含义
dp[i][j]表示 长度为i,末尾项为nums1[i-1]的数组,与长度为j,末尾项为nums2[j-1]的数组,二者的 最长公共后缀子数组长度。

2.确定状态转移方程
在两个数组中各抽出一个前缀数组,比较两个前缀数组的末尾项,
如果末尾项不相同,则以它们为末尾项的公共子数组长度为0,即dp[i][j]=0;
如果末尾项相同,则以它们为末尾项的公共子数组长度起码为1,即dp[i][j]>=1,这时就要再考虑它们的前缀数组:

如果它们俩前缀数组的末尾项不相同,那么前缀数组提供的的公共长度为0,即dp[i-1][j-1]=0(这里并不是说前缀数组的公共长度为0),则dp[i][j]=1
如果它们俩前缀数组的末尾项相同,那么前缀数组提供的公共长度至少为1,即dp[i-1][j-1]>=1,则dp[i][j] = dp[i-1][j-1] + 1
在这里插入图片描述
3.dp数组初始化
如果 i==0 || j == 0,则二者没有公共部分,dp[0][0]=0
记录dp[i][j]最大值,即最长的公共子数组长度

4.确定遍历顺序
都可以,本题用外层for循环遍历nums1,内层for循环遍历nums2。

5.举例推导dp数组
在这里插入图片描述

class Solution {
public:
    int findLength(vector<int>& nums1, vector<int>& nums2) {
        vector<vector<int>> dp(nums1.size() + 1, vector<int>(nums2.size() + 1, 0));
        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;
                if (dp[i][j] > result) result = dp[i][j];
            }
        }
        return result;
    }
};

题目4:1143.最长公共子序列

在这里插入图片描述
和718相比,本题不再求连续的公共子序列,求相对顺序相同的公共子序列,但是其实思路可是借鉴
在题解区也有位大佬讲的非常清楚,结果718的题解应该就非常明了了:最长公共子序列 | 图解DP | 最清晰易懂的讲解 【c++/java版本】

1.确定dp数组以及下标的含义
定义 f[i][j]表示字符串text1的[1,i]区间和字符串text2的[1,j]区间的最长公共子序列长度(下标从1开始)

2.确定状态转移方程
若text1[i] == text2[j] ,
即两个字符串的最后一位相等,那么问题就转化成了字符串text1的[1,j-1]区间和字符串text2的[1,j-1]区间的最长公共子序列长度再加上一,即dp[i][j] = dp[i - 1][j - 1] + 1
在这里插入图片描述
若text1[i] != text2[j],
即两个字符串的最后一位不相等,那么字符串text1的[1,i]区间和字符串text2的[1,j]区间的最长公共子序列长度无法延长,
因此dp[i][j]就会继承dp[i-1][j]与dp[i][j-1]中的较大值,即dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
在这里插入图片描述
因此状态转移方程为:
当text1[i] == text2[j],dp[i][j] = dp[i - 1][j - 1] + 1;
当text1[i] != text2[j],dp[i][j] = max(dp[i - 1][j],dp[i][j - 1])

3.dp数组初始化
f[i][0] = f[0][j] = 0,空字符串与有长度的字符串的最长公共子序列长度为0

注:
我们定义的状态表示dp数组和text数组下标均是从1开始的,而题目给出的text数组下标是从0开始的,为了一 一对应,在判断text1和text2数组的最后一位是否相等时,往前错一位,即使用text1[i - 1]和text2[j - 1]来判断。

为什么f数组和text数组均定义成下标从1开始:
因为状态转移方程 f[i][j] = max(f[i - 1][j],f[i][j - 1]), dp数组定义成下标从1开始以后,就可以在代码中不用对下标越界问题做出额外判断。

题目给定的原数组,比如text数组,如果下标从1开始的话,状态表示会更加的清晰,推导状态转移方程的过程也会更加好理解。

class Solution {
public:
    int longestCommonSubsequence(string text1, string text2) {
        vector<vector<int>> dp(text1.size() + 1, vector<int>(text2.size() + 1, 0));
        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()];
    }
};

题目5:1035.不相交的线

在这里插入图片描述
这道题的关键在于理解题意
直线不能相交,这就是说明在字符串A中 找到一个与字符串B相同的子序列,且这个子序列不能改变相对顺序,只要相对顺序不改变,链接相同数字的直线就不会相交
其实就是求两个字符串的最长公共子序列的长度

这么一转变其实就是和1143是一个题,甚至代码都不用变

class Solution {
public:
    int maxUncrossedLines(vector<int>& nums1, vector<int>& nums2) {
        vector<vector<int>> dp(nums1.size() + 1, vector<int>(nums2.size() + 1, 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]);
            }
        }
        return dp[nums1.size()][nums2.size()];
    }
};

题目6:53.最大子序和

在这里插入图片描述
1.确定dp数组以及下标的含义
dp[i]表示以nums[i]结尾的最大连续子序列的和。

2.确定状态转移方程
以 nums[i] 结尾的连续子数组与以 nums[i - 1] 结尾的连续子数组只相差一个元素 nums[i] 。

假设数组 nums 的值全都严格大于 00,那么一定有 dp[i] = dp[i - 1] + nums[i]。

但dp[i-1]有可能是负数,此时dp[i] = nums[i]

因此状态转移方程为dp[i] = max(dp[i-1] + nums[i], nums[i])

3.dp数组初始化
dp[0]=nums[0],即最小的子序列的和

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

5.举例推导dp数组
在这里插入图片描述

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        if (nums.size() == 0) return 0;
        vector<int> dp(nums.size());
        dp[0] = nums[0];
        int result = dp[0];
        for (int i = 1; i < nums.size(); i++) {
            dp[i] = max(dp[i - 1] + nums[i], nums[i]); 
            if (dp[i] > result) result = dp[i]; // result 保存dp[i]的最大值
        }
        return result;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值