【LeetCode-300】最长递增子序列

问题

给定一个无序的整数数组,找到其中最长递增子序列的长度。

示例

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

解答1:动态规划

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

重点思路

记动态规划数列dp,用于存储以该数结尾的最长上升子序列长度,最终输出dp数列中最大的值。时间复杂度O(n^2)。

解答2:贪心算法+二分查找

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        if (nums.empty()) return 0;
        vector<int> res{nums[0]};
        for(int i = 1; i < nums.size(); i++) {
            if (res.back() < nums[i]) res.push_back(nums[i]);
            else { // 二分查找
                int left = 0, right = res.size() - 1;
                while (left < right) {
                    int mid = left + (right - left) / 2;
                    if (lis[mid] < nums[i]) left = mid + 1;
                    else right = mid;
                }
                res[left] = nums[i];
            }
        }
        return res.size();
    }
};

重点思路

考虑一个简单的贪心,如果我们要使上升子序列尽可能的长,则我们需要让序列上升得尽可能慢,因此我们希望每次在上升子序列最后加上的那个数尽可能的小。

维护一个数列lis,用于存放上升最慢的递增子序列。首先遍历输入数组,当前数的值为nums[i],如果该数大于lis中最后的数,则pushback到最后,否则查找比nums[i]小且差的绝对值最小的一个数,用nums[i]替换该数对应的后一个数,保证递增的前提下使该序列上升得尽可能慢。

然后就是找这个需要替换的数的问题了。因为是递增数列,所以肯定用二分查找快。由上面的推导可知,从lis中取一个数,当这个数比nums[i]小时,那么需要替换的数肯定在这个数的后面。所以可以得到二分查找最关键的一句判断if (lis[mid] < nums[i]) left = mid + 1,剩下的就无脑写了。时间复杂度为O(nlog(n))。

解答3:贪心算法+二分查找(库函数lower_bound)

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        if (nums.empty()) return 0;
        vector<int> res{nums[0]};
        for(int i = 1; i < nums.size(); i++) {
            if (res.back() < nums[i]) res.push_back(nums[i]);
            else *lower_bound(res.begin(), res.end(), nums[i]) = nums[i];
        }
        return res.size();
    }
};

拓展

输出nums的最长递增子序列。(如果有多个答案,请输出其中 按数值(注:区别于按单个字符的ASCII码值)进行比较的 字典序最小的那个)

解答1:贪心算法+二分查找+恢复序列

class Solution {
public:
    vector<int> LIS(vector<int>& nums) {
        if (nums.empty()) return {};
        int n = nums.size();
        int maxLen[n]; maxLen[0] = 1;
        vector<int> res{nums[0]};
        for (int i = 1; i < n; i++) {
            if (res.back() < nums[i]) {
                res.push_back(nums[i]);
                maxLen[i] = res.size();
            }
            else {
                int pos = lower_bound(res.begin(), res.end(), nums[i]) - res.begin();
                res[pos] = nums[i];
                maxLen[i] = pos + 1; // 以当前数字结尾的最长递增子序列
            }
        }
        for (int i = res.size() - 1, j = n - 1; i >= 0; j--) // i for res, j for nums
            if (maxLen[j] == i + 1) res[i--] = nums[j];
        return res;
    }
};

重点思路

先使用贪心算法得到最长子序列长度,同时记录以当前位置结尾的子数组的最长递增子序列。最后我们得到一个maxLen数组,例如[1,2,3,2,3],我们需要判断maxLen[2]maxLen[4]选哪个。由于我们需要字典序最小,如果我们选maxLen[2],则意味着必须保证nums[2] < nums[4],此时maxLen[4] = 4,矛盾,所以有相同的数时取靠后的。

posted @ 2021-02-07 18:03  tmpUser  阅读(115)  评论(0编辑  收藏  举报