代码随想录算法训练营day52|第九章 动态规划part13

目录

300.最长递增子序列 

674. 最长连续递增序列 

718. 最长重复子数组 


300.最长递增子序列 

今天开始正式子序列系列,本题是比较简单的,感受感受一下子序列题目的思路。 

视频讲解:动态规划之子序列问题,元素不连续!| LeetCode:300.最长递增子序列_哔哩哔哩_bilibili

代码随想录

首先要明确的就是dp数组的意义,dp[i]代表以nums[i]为结尾的递增子序列的最大长度,只有这样每次比较nums[i]和nums[j]的大小才会有意义(看是否能在以nums[j]为结尾的子序列中加入nums[i]),这个超级重要。明确了这一点,要理解需要两层嵌套遍历就容易一点,要依次更新每一个dp[i],就必须每次都遍历一整遍前面的nums,一旦发现有小于当前nums[i]的,就证明当前nums[i]可以作为原来(以nums[j]为结尾的)子序列的新结尾,并且试图更新dp[i]的值,注意外层遍历从下标1开始(0前面没有元素,不可以用来更新dp[0]),内层循环从0开始到 i 前结束(遍历所有可能成为  以nums[i]结尾子序列的倒数第二个值  的数)。默认最小子序列为1,所以自然不必说dp数组需要全部初始化为1。最后,因为最长子序列的结尾元素不一定是nums最后一个元素,所以需要另设置res来实时更新最长子序列长度。

int lengthOfLIS(vector<int>& nums) {
        if (nums.size() <= 1) return nums.size();
        vector<int> dp(nums.size(), 1);
        int result = 0;
        for (int i = 1; i < nums.size(); i++) {
            for (int j = 0; j < i; j++) {
                if (nums[i] > nums[j]) dp[i] = max(dp[i], dp[j] + 1);
            }
            if (dp[i] > result) result = dp[i]; // 取长的子序列
        }
        return result;
    }

674. 最长连续递增序列 

本题相对于昨天的动态规划:300.最长递增子序列 最大的区别在于“连续”。 先尝试自己做做,感受一下区别  

视频讲解:动态规划之子序列问题,重点在于连续!| LeetCode:674.最长连续递增序列_哔哩哔哩_bilibili

代码随想录

这道题是求连续增加的子序列的最大长度,比上一题简单一些。

贪心算法——

因为是连续增加,所以只要判断当前nums值大于前一个,就size++,而一旦后一个不大于前一个,那就证明这一个连续递增子序列结束、新的子序列开始,所以需要回正size为初始值1,每次更新了size就要同步试图更新res。

int findLengthOfLCIS(vector<int>& nums) {
        int size=1,res=1;
        for(int i=1;i<nums.size();i++){
            if(nums[i]>nums[i-1]) size++;
            else size=1;
            if(size>res) res=size;
        }
        return res;
    }

动态规划——

这回的dp[i]意味着以nums[i]为结尾的最长 连续 递增子序列长度,因为连续所以以nums[i]为结尾的子序列的前一个值必然是nums[i-1],故而不用再开一个内层循环来遍历谁可能是  最长以nums[i]结尾的子序列  的前一个值。其他一样。

int findLengthOfLCIS(vector<int>& nums) {
        vector<int> dp(nums.size(),1);
        int res=1;
        for(int i=1;i<nums.size();i++){
            if(nums[i]>nums[i-1]) dp[i]=dp[i-1]+1;
            if(dp[i]>res) res=dp[i];
        }
        return res;
    }

718. 最长重复子数组 

稍有难度,要使用二维dp数组了

视频讲解:动态规划之子序列问题,想清楚DP数组的定义 | LeetCode:718.最长重复子数组_哔哩哔哩_bilibili

代码随想录

这道题需要使用二维数组来记录情况,这是因为dp[i][j]代表以nums1[i]/nums2[j]为结尾的相同子数组的最大长度,题目考察到两个数组,一旦遇到相等的元素,需要同时查看两个数组的上一个元素的情况(在dp[i-1][j-1]的值上加一),而使用二维数组来记录就刚好。文章选择开辟数组长度加一的二维数组,空出dp[0][]、dp[][0]这一行列不赋予意义,而是用dp[i+1][j+1]代表以nums[i]为结尾的最长相同子数组长度,这是因为如果第一个相等的元素恰好就是数组的第一个值的话,就没法使用递推公式来赋值了(nums[0]前面没有值,用来在此基础上加一计算dp[0]),需要特别处理,除非在前面多设置一行列赋值为0。

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;
    }

这道题也可以使用滚动数组的方式来减少空间的消耗,这是因为要求的是相等的子数组的长度,所以也可以就着某一个数组来比较,当然还是不能避免遍历两个数组,但是空间可以重叠。这时dp[j+1]就表示以nums2[j]为结尾的相同子数组的最大长度,递推公式变为dp[j+1]=dp[j]+1,还是要都初始化为0,道理不变。但是因为递推dp[j+1]的时候要用到dp[i][j]的元素(见下图),所以前一列的值是不能先变的,也就是dp[j]不能比dp[j+1]先改变。还有一点就是一旦遇到两个值不相等了,那就要立即设置dp[j+1]=0,之前使用二维数组的时候不用是因为递推dp[i][j]的时候,直接利用坐上角的值,如果这个i、j下标对应的两个值不等的话,是不会更新的(也就是还是初始化的0),而要使用一维数组的话就不一定了,如果不及时清零,那么这个数只是历史值,而不会代表dp[i-1][j-1]的值,就不满足递推公式了,而且放心你的res是及时同步更新的不会错过每一个不为0的值。

int findLength(vector<int>& nums1, vector<int>& nums2) {
        vector<int> dp(vector<int>(nums2.size()+1,0));
        int res=0;
        for(int i=0;i<nums1.size();i++){
            for(int j=nums2.size()-1;j>=0;j--){
                if(nums1[i] == nums2[j]){
                    dp[j+1]=dp[j]+1;
                }else dp[j+1]=0;
                if(dp[j+1]>res) res=dp[j+1];
            }
        }
        return res;
    }

 如果使用dp[i][j]来代表以nums1[i]/nums2[j]为结尾的相同子数组的最大长度的话,需要特别处理两个数组第一个值就相同的情况,且因为可能存在这种情况,需要试图更新res的值,而这也就决定了不能在遍历两个数组的时候(更新其他dp数组的值的时候)不遍历下标为0的元素,或者说选择在初始化dp[0]的时候就直接判断res是否能为1,总之不能不考虑0下标的相等情况。

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 = 0; i < nums1.size(); i++) if (nums1[i] == nums2[0]) dp[i][0] = 1;
        for (int j = 0; j < nums2.size(); j++) if (nums1[0] == nums2[j]) dp[0][j] = 1;

        for (int i = 0; i < nums1.size(); i++) {
            for (int j = 0; j < nums2.size(); j++) {
                if (nums1[i] == nums2[j] && i > 0 && j > 0) { // 防止 i-1 出现负数
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                }
                if (dp[i][j] > result) result = dp[i][j];
            }
        }
        return result;
    }
  • 16
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
代码随想录算法训练是一个优质的学习和讨论平台,提供了丰富的算法训练内容和讨论交流机会。在训练中,学员们可以通过观看视频讲解来学习算法知识,并根据讲解内容进行刷题练习。此外,训练还提供了刷题建议,例如先看视频、了解自己所使用的编程语言、使用日志等方法来提高刷题效果和语言掌握程度。 训练中的讨论内容非常丰富,涵盖了各种算法知识点和解题方法。例如,在第14天的训练中,讲解了二叉树的理论基础、递归遍历、迭代遍历和统一遍历的内容。此外,在讨论中还分享了相关的博客文章和配图,帮助学员更好地理解和掌握二叉树的遍历方法。 训练还提供了每日的讨论知识点,例如在第15天的讨论中,介绍了层序遍历的方法和使用队列来模拟一层一层遍历的效果。在第16天的讨论中,重点讨论了如何进行调试(debug)的方法,认为掌握调试技巧可以帮助学员更好地解决问题和写出正确的算法代码。 总之,代码随想录算法训练是一个提供优质学习和讨论环境的平台,可以帮助学员系统地学习算法知识,并提供了丰富的讨论内容和刷题建议来提高算法编程能力。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [代码随想录算法训练每日精华](https://blog.csdn.net/weixin_38556197/article/details/128462133)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值