300. 最长上升子序列

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

示例:

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

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

本题的难点就在于复杂度降低到O(nlog n),如果仅仅是o(n*n),那么用普通的动归就可以解决本题了,思路很简单,状态转移就是 if if(nums[j] >nums[i]) dp[j] = dp[i] + 1。只要依次遍历即可。
代码如下:

class Solution {
    public int lengthOfLIS(int[] nums) {
        int n = nums.length;
        if (n == 0) return 0;
        int[] dp = new int[n];
        dp[0] = 1;
        for (int i = 1;i < n; i++){
            dp[i] = 1;
            for (int j = 0;j < i; j++){
                if (nums[i] > nums[j]){
                    if (dp[j] + 1 > dp[i]){
                        dp[i] = dp[j] + 1;
                    }
                }    
            }
        }
        int max = 1;
        for (int m : dp){
            if (max < m) max = m;
        }
        return max;
    }   
}

优化的思路,其实我认为是很难想到的,思路是这样的,我们可以维护一个数组tail[i]表示长度为 i + 1 的所有上升子序列的结尾的最小值。
原因在于我们可以发现,如果说我们每次取的值都尽量的小,那么这个上升子序列就会尽量的长。
思路是这个样子的,比较难想到,那该如何去做呢?
遍历整个数组:
1、当遇见的数比目前这个数组的最后值大,那么直接放到最后,数组长度加一。
2、如果遇见的数比当前这个数组的最后值小,那么就替换掉这个数组的第一个出现比这个值大的数,使得这个数组的结尾最小。
可能大家还是云里雾里的,我们来举个例子吧。
毕竟最好的理解一个算法,就是跟踪这个算法的过程。
[10,9,2,5,3,7,101,18]
我们初始化tail 长度为1 tail = {10}
1、遍历到9,由于9比10小,替换掉这个数组第一个出现比9大的10,tail={9}
2、遍历到2,由于2比9小,替换掉9,tail = {2}
3、遍历到5,由于5比2大,直接放到末尾,tail={2,5}
4、遍历到3,由于3比5小,替换掉这个数组第一个出现比3大的5,tail={2,3}
5、遍历到7,由于7比3大,直接放到末尾,tail={2,3,7}
6、遍历到101,由于101比7大,直接放到末尾,tail = {2,3,7,101}
7、遍历到18,由于18比101小,替换掉这个数组第一个出现比18大的101,tail={2,3,7,18}
结果就是tail的长度为4。
通过这个遍历我们可以看出来tail的数组其实并一定是最长上升子序列,它只是个辅助数组,tail[i]代表的是长度为i+1的上升子序列的结尾最小值,一定要牢记这个概念,这个思路是比较难想到的,需要练习记忆。
在寻找第一个出现的最小值,我们可以用二分查找法,因此一次遍历加上二分查找法的复杂度就是o(nlogn)
代码如下:

class Solution {
    public int lengthOfLIS(int[] nums) {
        int n = nums.length;
        if (n == 0) return 0;
        int[] tail= new int [n];
        //核心点,判断此时的tail长度
        int end = 0;
        tail[0] = nums[0];
        for (int i = 1; i < n; i++){
            if (nums[i] > tail[end]){
                tail[++end] = nums[i];
            }
            else{
                int low = 0;
                int high = end;
                while (low < high){
                    int mid = (high - low) / 2 + low;
                    if (tail[mid] < nums[i]){
                        low = mid + 1;
                    }
                    else {
                        high = mid;
                    }
                }
                tail[low] = nums[i]; 
            }
        }
        return ++end;
    }   
}

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/longest-increasing-subsequence
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

你可能感兴趣的:(300. 最长上升子序列)