【leetcode】300. 最长上升子序列(动态规划)

题目描述

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

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

说明:
可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
你算法的时间复杂度应该为 O(n2) 。

进阶: 你能将算法的时间复杂度降低到 O(n log n) 吗?

解题思路

1. 动态规划,时间复杂度O(n2)

定义数组dp[i],长度为输入数组nums的长度nums.length,dp[i]表示以数组nums下标为i的元素为结尾的最大子序列长度。
对于任意i属于[0, nums.length),先令dp[i]=1,因为不论前面是否有小于nums[i]的数,nums[i]都可以自己组成一个子序列,长度为1。之后,对i之前的数nums[j](j属于[0, i))进行搜索,判断是否存在小于nums[i]的数nums[j],如果存在,则说明nums[i]可以和nums[j]所在上升子序列相结合,并使得序列长度+1,即长度变成dp[j]+1。dp[i]最终取{dp[j]+1, 1}中最大值(其中0<=j<i)。

在遍历全数组nums之后,得到完整的dp[]数组,遍历dp数组,选择值最大的即为最长上升子序列的长度,返回max(dp[i])。

代码

class Solution {
    public int lengthOfLIS(int[] nums) {
        if(nums==null || nums.length<=0)
            return 0;

        int len = nums.length;
        int[] dp = new int[len];

        for(int i=0; i<len; i++){
            dp[i] = 1;
            for(int j=0; j<i; j++){
                if(nums[i]>nums[j]){
                    dp[i] = Math.max(dp[j]+1, dp[i]);
                } 
            }
        }

        int max = 0;
        for(int i=0; i<len; i++){
            max = Math.max(max, dp[i]);
        }
        return max;
    }
}

2. 贪心+二分查找,时间复杂度O(nlogn)

贪心思想:如果要使上升子序列尽量长,则应使子序列上升地比较慢,因此要使子序列中最后加上的值尽量小。
基于这个思想,维护一个数组d[i],表示长度为i的上升子序列的最小结尾值。并用size表示当前已知的最长上升子序列长度,起始时size=1。
遍历数组nums,对数字nums[i]进行判断:
(1) 如果nums[i]>d[size],则直接将nums[i]加在最长子序列的最后,并更新dp和size:d[++size] = nums[i];
(2) 否则,在已知的上升子序列中(j属于[1,size]),找到第一个小于nums[i]的数(其下标为j),并将d[j+1]=nums[i]。(因为nums[j]<nums[i]<nums[j+1],因此用nums[i]替换nums[j+1]。)
根据数组d的单调上升性,可以使用二分查找算法,在数组d的[1,size]范围内查找第一个小于nums[i]的数。
最后,返回size即为最长上升子序列长度。

代码

class Solution {
    public int lengthOfLIS(int[] nums) {
        if(nums==null || nums.length<=0)
            return 0;
        
        int len = nums.length;
        int[] d = new int[len+1];
        int size = 1;
        d[size] = nums[0];
        
        for(int i=1; i<len; i++){
            if(nums[i]>d[size]){
                d[++size] = nums[i];
            }
            else{
                int left = 1;
                int right = size;
                int pos = 0;
                while(left<=right){
                    int mid = (left+right)>>1;
                    if(d[mid]<nums[i]){
                        pos = mid;
                        left = mid+1;
                    }
                    else{
                        right = mid-1;
                    }
                }
                d[pos+1] = nums[i];
            }
        }

        return size;
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值