• [LeetCode 300.] 最长递增子序列


    LeetCode 300. 最长递增子序列

    题目描述

    给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。

    子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。

    示例 1:

    输入:nums = [10,9,2,5,3,7,101,18]
    输出:4
    解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。

    示例 2:

    输入:nums = [0,1,0,3,2,3]
    输出:4

    示例 3:

    输入:nums = [7,7,7,7,7,7,7]
    输出:1

    提示:

    • 1 <= nums.length <= 2500
    • -104 <= nums[i] <= 104

    进阶:

    • 你可以设计时间复杂度为 O(n2) 的解决方案吗?
    • 你能将算法的时间复杂度降低到 O(n log(n)) 吗?

    解题思路

    思路一:

    首先找出以 nums[i] 为末尾元素的最长递增子序列,然后取最大值即为全局最长递增子序列。
    时间复杂度 O(n^2) 空间复杂度 O(n)。

    思路二:

    从1开始,找出长度为k的递增子序列的最小末尾元素值,然后就可得知自增子序列最大长度。
    为何是最小末尾元素值?是为了让递增子序列可以尽可能最长。
    时间复杂度 O(n^2) 空间复杂度 O(n)。

    思路三:

    继续沿着思路二思考,我们观察维护的这个最小末尾元素序列,可以发现其必然是递增序列。此处可用反证法证明。
    有了这个发现之后,我们就可以用二分查找来优化我们算法的时间复杂度了。
    时间复杂度 O(nlogn) 空间复杂度 O(n)。

    参考代码

    解法一:

    class Solution {
    public:
        int lengthOfLIS(vector<int>& nums) {
            size_t n = nums.size();
            vector<int> len(n, 1); // LIS end up with nums[i]
            for (size_t i = 0; i<n; i++) {
                for (size_t j = 0; j<i; j++) {
                    if (nums[j] < nums[i]) {
                        len[i] = max(len[i], len[j] + 1);
                    }
                }
            }
            int maxlen = 0;
            for (int x : len) {
                if (x > maxlen) {
                    maxlen = x;
                }
            }
            return maxlen;
        } // O(n^2) time O(n) space
    };
    

    解法二:

    class Solution {
    public:
        int lengthOfLIS(vector<int>& nums) {
            vector<int> tail;
            tail.push_back(INT32_MIN); // guard, for len=1 update
            for (int x : nums) {
                for (int64_t i = tail.size() - 1; i >= 0; i--) {
                    if (tail[i] < x) {
                        if (i == tail.size() - 1) {
                            tail.push_back(x);
                        } else {
                            tail[i+1] = min(tail[i+1], x);
                        }
                    }
                }
            }
            return tail.size() - 1;
        } // O(n^2) time O(n) space
    };
    

    这里用了一个哨兵,用来解决长度为1的递增子序列的最小末尾值更新,使其和长度大于1的使用相同逻辑来维护。

    解法三:

    class Solution {
    public:
        int lengthOfLIS(vector<int>& nums) {
            vector<int> tail;
            tail.push_back(INT32_MIN); // guard, for len=1 update
            for (int x : nums) {
                auto it = lower_bound(tail.begin(), tail.end(), x);
                if (it == tail.end()) {
                    tail.push_back(x);
                } else {
                    *it = x;
                }
            }
            return tail.size() - 1;
        } // O(nlogn) time O(n) space
    };
    

    还有一种经典写法更精简,充分利用了 lower_bound 返回值是 大于或等于 x 的第一个元素位置 这一特点:

        int lengthOfLIS(vector<int>& nums) {
            size_t n = nums.size();
            vector<int> tail(n, INT32_MAX);
            for (int x : nums) {
                *lower_bound(tail.begin(), tail.end(), x) = x;
            }
            return lower_bound(tail.begin(), tail.end(), INT32_MAX) - tail.begin();
        } // O(nlogn) time O(n) space
    
  • 相关阅读:
    awk,seq,xarg实例使用
    Docker安装yapi
    基于阿里搭载htppd访问
    锐捷结课作业
    基于centos7搭建kvm
    基于django实现简易版的图书管理系统
    python 自定义log模块
    Interesting Finds: 2008.01.13
    Interesting Finds: 2008.01.24
    Interesting Finds: 2008.01.17
  • 原文地址:https://www.cnblogs.com/zhcpku/p/15060837.html
Copyright © 2020-2023  润新知