LeetCode第 300 题:最长上升子序列(C++)

300. 最长上升子序列 - 力扣(LeetCode)
在这里插入图片描述

动态规划

动态规划 、优化(以贪心和二分作为子过程) - 最长上升子序列 - 力扣(LeetCode)

为了从一个较短的上升子序列得到一个较长的上升子序列,主要关心这个较短的上升子序列结尾的元素。由于要保证子序列的相对顺序,在程序读到一个新的数的时候,如果比已经得到的子序列的最后一个数还大,那么就可以放在这个子序列的最后,形成一个更长的子序列;

假设dp[i]表示以元素nums[i]为最大值的子序列长度,dp[i]之前的元素值已知,那应该怎么计算dp[i]呢?

对nums[i]之前的(下标小于i)的每一个小于nums[i]的元素(只有这些元素才可以和nums[i]组成上升序列),假设这些元素集合为A,那么:

dp[i] = max( f[x] for x in A ) + 1;

这是一个套娃的过程,这么理解:nums[i]之前的那些比它要小的元素,都可以和它组成上升序列,而我们肯定想找能够组成的序列长度更长的那个。看代码注释:

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        int n = nums.size(), res = 0;
        vector<int> dp(n, 1);//以nums[i]为最大值的子序列长度
        for(int i = 0; i < n; ++i){
        	//查找nums[i]前面所有比nums[i]小的元素的最大子序列长度
            for(int j = 0; j < i; ++j){
                if(nums[j] < nums[i])   dp[i] = max(dp[i], 1+dp[j]);
            }
            res = max(res, dp[i]);
        }
        return res;
    }
};

代码的复杂度,双层循环显然为O(n^2),外层循环显然不可避免,那内层循环可以优化吗?

贪心 + 二分

原来是使用贪心,居然没想到,类似于维护一个单调栈。

我们需要的是尽可能长的上升序列,那么对于同样长度的上升序列,肯定是尾元素越小越好

方法一,例子中的nums前三个元素为10, 9, 2,对应的上升序列长度都是1,但是 2 应该被保留,10, 9则可以舍弃掉,因为后面的元素如果可以接在10/9后面形成上升序列,那一定也可以接在2后面,反之则未必。

所以我们维护一个单调栈f,f[i]表示长度为i的最长上升子序列的末尾元素的最小值,还是上面的例子:
10进来的时候,以10结尾的最长上升序列长度为1,那么f[1] = 10;
9进来的时候,以9结尾的最长上升序列长度也为1,长度都是1,但9 < 10,更新f[1] = 9;
2进来的时候长度也为1,2 < 9,更新f[1] = 2

可以去这儿看ppt:
动态规划 、优化(以贪心和二分作为子过程) - 最长上升子序列 - 力扣(LeetCode)

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        int n = nums.size();
        if(n < 2) return n;
        vector<int> f;//f[i]最大子序列长度为i的对应nums元素最小值
        f.push_back(nums[0]);//先push第一个
        for(int i = 1; i < n; ++i){
            if(nums[i] > f.back())  f.push_back(nums[i]);
            else{
                auto it = lower_bound(f.begin(), f.end(), nums[i]);
                *it = nums[i];
            }
        }
        return f.size();
    }
};

这个思路真的很厉害。

上面的代码可以简洁一点:

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        vector<int> f;//f[i]最大子序列长度为i的对应nums元素最小值
        for(int i = 0; i < nums.size(); ++i){
            if(f.empty() || nums[i] > f.back())  f.push_back(nums[i]);
            else    *lower_bound(f.begin(), f.end(), nums[i]) = nums[i];
        }
        return f.size();
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值