(Java) LeetCode 300. Longest Increasing Subsequence —— 最长上升子序列

Given an unsorted array of integers, find the length of longest increasing subsequence.

Example:

Input: [10,9,2,5,3,7,101,18]
Output: 4 
Explanation: The longest increasing subsequence is [2,3,7,101], therefore the length is 4. 

Note:

  • There may be more than one LIS combination, it is only necessary for you to return the length.
  • Your algorithm should run in O(n2) complexity.

Follow up: Could you improve it to O(n log n) time complexity?


 

解法一,O(n2):

直观想一下,选中一个元素,以这个元素为结尾的子序列前面有多少个比他值小的元素,那么以它为结尾的上升子序列就是多少再加一。即当前状态可以由之前的一个或一些状态推导出来,所以可以用动态规划。建立一个一维数组dp,dp[i]表示以nums[i]结尾的最长上升子序列长度。初始都置为1。对于原数组每个元素,二重循环从头遍历原数组,每当找到一个比当前元素小的值,证明至少可以形成一个dp[j]+1的上升子序列,所以dp[i] = max(dp[i], dp[j] + 1),而dp[j]之前已经求得。

 

解法二,O(n log n):

还是想一下“人工”做这个题是什么过程。按照上面的例子来分析:

首先看到10,加入备选集,备选集合为{10};

之后看到了9,没有形成上升序列,那么9不应该加入备选集合。但是因为9小于10,所以如果把10替换成9会增加接下来产生上升序列的机会,且并不影响备选集合元素的个数(因为是替换),所以替换掉,备选集现在有{9};

遇到2道理同上,替换掉9,备选集变成{2};

遇到5,这时候形成了上升序列,此时应该是添加到备选集合,变为{2,5};

遇到3,没有形成上升序列,但还是道理同加入9的情况,如果此时把5替换成3,会增加接下来形成上升序列的机会,且备选集保持上升,并且个数也没变,所以替换掉5,备选集变成{2,3};

遇到7,同遇到5,添加元素,备选集{2,3,7};

遇到101,同上,备选集{2,3,7,101};

遇到18,还是一样,虽然没有形成上升序列,但是如果把101替换掉,那么接下来形成上升序列的机会会增加,并且备选集的上升属性和元素个数都不变,所以替换,备选集变为{2,3,7,18}。

至此所有元素添加完毕,备选集的元素个数就是最长上升子序列长度。但这里注意,备选集里面的元素并不是最后最长子序列的元素。因为在寻找最长子序列的过程中,目标是尽可能的让以后形成上升序列的机会增加,所以进行了替换。

“人工”做出来之后,只要用程序实现思考过程就好。总结起来就是:

如果遇到的元素比备选集合里面的元素都大,那么就添加进去,使得上升序列长度增加;

如果遇到的元素比备选集合里最后一个元素小,那么代表它无法被添加到备选集。但是为了使后面得到上升序列的机会增加,需要在不破坏集合上升属性和元素总数的情况下,替换掉备选集中的元素,那么就是替换掉大于他的元素中最小的那个,这样才能满足条件。

这时候,发现备选集一直是保持有序,寻找替换元素的时候就可以用到二分查找,得到O(n log n)的时间复杂度。其中还要注意的是如果元素已经在备选集合中,是不需要任何操作的,因为它并不能增加上升序列的长度,也不会增加之后遇到上升序列的机会,所以直接跳过。

 


解法一(Java)

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

 

解法二(Java)

class Solution {
    public int lengthOfLIS(int[] nums) {
        if (nums == null || nums.length == 0) return 0;
        List<Integer> dp = new ArrayList<>();
        dp.add(nums[0]);
        for (int i = 1; i < nums.length; i++) {
            if (dp.contains(nums[i])) continue;
            else if (nums[i] > dp.get(dp.size()-1)) dp.add(nums[i]);
            else if (nums[i] < dp.get(dp.size()-1)) {
                int l = 0, r = dp.size()-1;
                while (l < r) {
                    int mid = l + (r - l) / 2;
                    if (dp.get(mid) < nums[i]) l = mid + 1;
                    else r = mid;
                }
                dp.set(r, nums[i]);
            }
        }
        return dp.size();
    }
}

 

转载于:https://www.cnblogs.com/tengdai/p/9241455.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值