最长上升子序列(LIS)

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

示例:

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

可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
你算法的时间复杂度应该为 O(n2) 。
进阶: 你能将算法的时间复杂度降低到 O(n log n) 吗?

 

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        int n = nums.size();
        if(n == 0) return 0;
        if(n == 1) return 1;
        int len = 1;
        vector<int> dp;
        dp.push_back(nums[0]);
        for(int i = 1; i < n; i++){
            if(nums[i] > dp[len-1]){
                dp.push_back(nums[i]);
                len++;
            }
            else{
                int k = search(dp, nums[i]);
                dp[k+1] = nums[i];
            }
        }
        return len;
    }
    
    int search(vector<int> &arr, int x){
        int low = 0;
        int high = arr.size()-1;
        int k = (low+high)/2;
        while(!(arr[k] < x && x <= arr[k+1]) && high > low){
            if(arr[k+1] < x){
                low = k+1;
                k = (low+high)/2;
            }
            else if(x <= arr[k]){
                high = k-1;
                k = (low+high)/2;
            }
        }
        if(high > low) return k;
        else if(x <= arr[k]) return -1;
        else return 0;
    }
};

动态转移方程为: dp[i] = max(dp[j]) + 1, 其中 0<= j <= i-1, dp[i] 表示以 nums[i] 结尾的上升子序列的长度

根据DP方程,很显然你每次计算以 nums[i] 为结尾的上升子序列长度的时候,都要从 0 到 i-1遍历一下dp[],这样下时间复杂度就是O(n2)了

其实可以用到贪心的思想降低时间复杂度:

想要上升子序列最长,那就是每次上升的时候增幅小一点

所以可以设置一个数组,其中记录每个长度下最后一个元素的最小值。

则每次计算 dp[i] 的时候,就可以不用遍历一遍dp了

而只需要对 dp 进行二分查找,这么一来,遍历dp的时间复杂度就由 O(n) 降到了 O(log n),整体的时间复杂度降为 O(n log n)

为什么可以对dp进行二分查找呢?因为dp是单调递增的。

上一篇:js操作元素属性


下一篇:学习java第48天