算法学习 | day45/60 最长递增子序列/最长连续递增序列/最长重复子数组

本文详细解析了LeetCode中的三个问题:最长递增子序列、最长连续子序列和最长重复子数组的动态规划解决方案,强调了状态转移方程和优化策略,如单调栈和二分查找的应用。
摘要由CSDN通过智能技术生成

一、题目打卡

        1.1  最长递增子序列

        题目链接:. - 力扣(LeetCode)

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        if(nums.size() <= 1) return 1; //题目写明:1 <= nums.length <= 2500
        vector<int> dp(nums.size(), 1);
        int res = INT_MIN;

        // 以nums【i】为结尾的最长递增子序列的长度可以由  nums【0】为结尾的最长递增子序列长度、nums[1为结尾的最长长度、……nums【i-1】为结尾的最长长度 比较得到
        for(int i = 1; i < nums.size();i++){
            for(int j = 0; j < i;j++){
                if(nums[i] > nums[j]) dp[i] = max(dp[j] + 1, dp[i]);
                res = max(dp[i], res); // 如果后面出现了更小的数字,那后面的dp数组是会更小的
            }
        }
        // for(auto &it :dp){
        //     cout << it <<" ";
        // }
        // cout << endl;
        // return dp[nums.size() -1];
        return res;
    }
};

        看到了一句评论,觉得总结的特别到位:

        以nums【i】为结尾的最长递增子序列的长度可以由 nums【0】为结尾的最长递增子序列长度、nums[1为结尾的最长长度、……nums【i-1】为结尾的最长长度比较得到。

       这里状态转移方程,需要一个比最大值的操作,因为存在第二层循环,其实也就是相当于第二次循环每次找到满足要求的,都进行相应的迭代。

        基本的思路也就是这样,注意一下dp数组并没有严格的单调性,所以这里返回的结果需要另外维护一个变量,但是这样其实时间复杂度挺高的,第一步遍历dp数组肯定是无法简化的,而遍历j的过程,其实可以用一个单调栈然后二分来降低一点复杂度,具体的代码:

# Dynamic programming + Dichotomy.
class Solution:
    def lengthOfLIS(self, nums: [int]) -> int:
        tails, res = [0] * len(nums), 0
        for num in nums:
            i, j = 0, res
            while i < j:
                m = (i + j) // 2
                if tails[m] < num: i = m + 1 # 如果要求非严格递增,将此行 '<' 改为 '<=' 即可。
                else: j = m
            tails[i] = num
            if j == res: res += 1
        return res

        1.2 最长连续子序列

        题目链接:. - 力扣(LeetCode)

class Solution {
public:
    int findLengthOfLCIS(vector<int>& nums) {
        if(nums.size() <= 1) return 1;
        vector<int> dp(nums.size(), 1);
        int res = 0;

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

        感觉这个题目比上一个题目要好理解一点,因为要求连续,所以就不存在再去遍历 j 到 i 的区间,只需要比较当前和前一位的一个大小情况就可以,思路也就直接地多。

        1.3 最长重复子数组

        题目链接:. - 力扣(LeetCode)

class Solution {
public:
    int findLength(vector<int>& nums1, vector<int>& nums2) {
        vector<vector<int>> dp(nums1.size(), vector<int>(nums2.size(), 0));
        int res = 0;
        for(int i = 0 ; i < nums1.size();i++){
            if(nums1[i] == nums2[0]){
                dp[i][0] = 1;
                res = 1;
            }
        }

        for(int i = 0 ; i < nums2.size();i++){
            if(nums2[i] == nums1[0]){
                dp[0][i] = 1;
                res = 1; //这里的res也一定要初始化
            }
        }

        for(int i = 1; i < nums1.size();i++){
            for(int j = 1 ; j < nums2.size(); j++){
                if(nums1[i] == nums2[j]) dp[i][j] = dp[i-1][j-1] + 1;
                res = max(res, dp[i][j]);
            }
        }
        return res;
    }
};

        这个题目的状态定义和之前的差不多,不过注意题目是子数组而不是子序列,所以要求必须要连续,这种情况下,其实递推的过程就会稍微简单一点,如果画了表格来表示的话,其实就是从左上角的那个位置转移过来的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值