LeetCode 300. 最长递增子序列

题目描述

300. 最长递增子序列

解法一:DP(C++)

我们记状态 d p [ i ] dp[i] dp[i] 表示以第 i i i 个元素结尾的最长上升子序列的长度,那么专一方程就可以定义为 d p [ i ] = m a x ( d p [ j ] ) + 1   ( 0 ≤ j < i   a n d   n u m s [ j ] < n u m s [ i ] ) dp[i]=max(dp[j]) + 1\ (0 \leq j<i\ and\ nums[j]<nums[i] ) dp[i]=max(dp[j])+1 (0j<i and nums[j]<nums[i])

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        int n = nums.size();
        if(n<=1) return n;
        vector<int> dp(n, 0);
        for(int i=0;i<n;i++)
        {
            dp[i] = 1;
            for(int j=0;j<i;j++)
                if(nums[j]<nums[i]) dp[i] = max(dp[i], dp[j]+1);
        }
         return *max_element(dp.begin(), dp.end());
    }
};

解法二:贪心+二分(C++)

详细参考 官方题解

讲真,最早最早想到这个算法的人吼,脑回路确实不是一般人。

先来说他是咋想的哈,我们要让上升子序列尽可能长,那么上升子序列一定不能上升得太快,也就是说右端点值不能一上来就很大很大。那么,贪心就体现在每次增长时在子序列最末尾添加的元素要尽可能小。

我们记一个 d [ l e n ] d[len] d[len] 来表示长度为 l e n len len 的上升子序列的末尾元素值。

易知的是, d [ l e n ] d[len] d[len] 关于 l e n len len 单调递增。有单调性我们就知道怎么出来的二分法了。

如果 n u m s [ i ] > d [ l e n ] nums[i]>d[len] nums[i]>d[len],就说明我们这时在 d [ . . . ] d[...] d[...] 的末尾插入 n u m s [ i ] nums[i] nums[i] 是合理的,保证上升性的同时增加了长度。

如果 n u m s [ i ] < d [ l e n ] nums[i]<d[len] nums[i]<d[len]呢 ?

通过上面的描述,我们知道 n u m s [ i ] nums[i] nums[i] 是有用的,它保证了这个子序列不会上升很快。但是我们应该把它放哪儿呢?插入到最后是不合适的,这样违背了上升性。于是,我们对 d [ . . . ] d[...] d[...] 做二分查找,找到一个 n u m s [ i ] nums[i] nums[i] 可以插进去的地方,这个位置必然满足 d [ p o s ] < n u m s [ i ] < d [ p o s + 1 ] d[pos]<nums[i]<d[pos+1] d[pos]<nums[i]<d[pos+1],这样我们就将 n u m s [ i ] nums[i] nums[i]插入到 p o s + 1 pos+1 pos+1 的位置,也就是说我们以 n u m s [ i ] nums[i] nums[i] 结尾的子序列长度为 p o s + 1 pos+1 pos+1,这样做既保证了最长上升子序列的要求,也满足了我们希望它上升得慢一些的要求

以输入序列 [0, 8, 4, 12, 2]为例:

第一步插入 0,d = [0]

第二步插入 8,d = [0, 8]

第三步插入 4,d = [0, 4]

第四步插入 12,d = [0, 4, 12]

第五步插入 2,d = [0, 2, 12]

最终得到最大递增子序列长度为 3

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        int n = nums.size(), len = 1;
        if(n<=1) return n;
        vector<int> d(n+1, 0);
        d[len] = nums[0];
        for(int i=1;i<n;i++)
        {
            if(nums[i]>d[len]) d[++len] = nums[i];
            else
            {
                int left = 1, right = len, pos = 0;
                while(left<=right)
                {
                    int mid = left+(right-left)/2;
                    if(d[mid]<nums[i])
                    {
                        pos = mid;
                        left = mid+1;
                    }
                    else right = mid-1;
                }
                d[pos+1] = nums[i];
            }
        }
        return len;
    }
};

解法三:耐心排序

耐心排序求最大上升子序列也是脑回路非一般人, 但是总的和解法二是很相近的,只是在解法二的基础再深了一点点 (@_@ ; )

我们先讲啥是耐心排序

假设数列是一组牌,每次都抽取一张,按规则放置,最后形成几组牌

规则是:

  • 每组牌都是从大到小排列,小的牌不能放在大的牌上
  • 放置牌时,从左至右查看,将牌放置能最左的能放置的组里,如果所有组都不能放置,那么这张牌在最右形成新的组。

假设数列是[0, 8, 4, 12, 2]

每次放置牌时,组的结果如下:
初始化 [ ]
第一步插入 0, [ [0] ]
第二步插入 8,[ [0], [8] ]
第三步插入 4,[ [0], [8, 4]]
第四步插入 12, [ [0], [8, 4], [12]]
第五步插入 2,[ [0], [8, 4, 2], [12]]

一共 3 组,取每个组的第一个数形成 [0, 8, 12],这是该数列的最长上升子序列

编码具体内容就不给出了,耐心排序的编码实现和解法二是一样的,只是在耐心排序中我们选择将每个较大元素单独设置成一个组,而不是直接插入末尾

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值