leetcode : [300. 最长递增子序列](https://leetcode-cn.com/problems/longest-increasing-subsequence/)

# leetcode : [300. 最长递增子序列](https://leetcode-cn.com/problems/longest-increasing-subsequence/)

给你一个整数数组 `nums` ,找到其中最长严格递增子序列的长度。

**子序列** 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,`[3,6,2,7]` 是数组 `[0,3,1,6,2,2,7]` 的子序列。

**示例 1:**

```
输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。
```

**示例 2:**

```
输入:nums = [0,1,0,3,2,3]
输出:4
```

**示例 3:**

```
输入:nums = [7,7,7,7,7,7,7]
输出:1
```

**提示:**

- `1 <= nums.length <= 2500`
- `-104 <= nums[i] <= 104`

**进阶:**

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

Related Topics

数组

二分查找

动态规划

## 思路1 : 动态规划

dp[i]表示i这个位置的最长子序列长度。默认自身长度为。

对于第i个位置的元素的递增子序列长度,都需要跟这个元素之前的元素进行比较

* 如果第i个位置的值大于之前的元素,那么就需要比较出dp[i]和dp[前面某个位置]+1的最大值。

```java
class Solution {
    public int lengthOfLIS(int[] nums) {
        int length = nums.length;
        int[] dp = new int[length];
        int max = 0;
        for(int i = 0; i < length;i++){
            dp[i] = 1;
            //对于第i个位置的元素的递增子序列长度,都需要跟这个元素之前的元素进行比较
            for(int j = 0 ; j < i;j++){
                //如果第i个位置的值大于之前的元素,那么就需要比较出dp[i]和dp[前面某个位置]+1的最大值。
                if(nums[i] > nums[j]){
                    dp[i] = Math.max(dp[i],dp[j]+1);
                }
            }
            if(dp[i] > max){
                max = dp[i];
            }
        }
        return max;
    }
}
解答成功:
            执行耗时:56 ms,击败了63.84% 的Java用户
            内存消耗:40.9 MB,击败了14.55% 的Java用户
            时间复杂度O(n^2)
```

## 思路2:贪心+二分查找

参考leetcode官方解答:

* 我们要使上升子序列尽可能长,就要让序列的上升尽可能慢,那就得使每次上升的数字尽可能小。

* 使用dp[i]表示长度为i的上升子序列末尾元素的最小值。使用max表示上升子序列的最大长度。

* 在dp中找到第一个小于当前遍历的元素dp[i-1]<num<dp[i],那么长度为i-1的子序列末尾最小值是dp[i-1],后面添加上num后,长度为i的子序列末尾最小值是num,小于dp[i],所以需要更新dp[i]。

* 因为dp[i]存储的是长度为i的末尾最小值,所以是单调递增的,可以使用二分查找。

算法思路:设max表示最长的子序列长度

* 如果nums[i] > dp[max],dp[++max] = nums[i]。
* 如果nums[i] <= dp[max],对dp进行二分查找,找到dp中第一个小于nums[i]的位置。

```java
class Solution {
    public int lengthOfLIS(int[] nums) {
        int length = nums.length;
        int[] dp = new int[length+1];
        //初始化 最长子序列长度为1 长度为1的最小值为nums[0]
        int max = 1;
        dp[max] = nums[0];
        for(int i = 1; i < length;i++){
            if(nums[i] > dp[max]){
                dp[++max] = nums[i];
            }else{
                int left = 1 ;
                int right = max;
                int pos = 0;  //记录dp中比nums[i]小的位置  如果找不到比nums的小的位置 默认为0
                while(left <= right){
                    int mid = (left+right)>>1;
                    if(dp[mid]<nums[i]){//此时第一个小于nums[i]的值一定在[mid,...)范围内
                        pos = mid;
                        left = mid + 1;
                    }else{ //在(...,mid-1)范围内
                        right = mid-1;
                    }
                }
                dp[++pos] = nums[i];
            }

        }
        return max;
    }
}
解答成功:
            执行耗时:2 ms,击败了99.73% 的Java用户
            内存消耗:41.1 MB,击败了5.09% 的Java用户
                时间复杂度O(nlogn)
```

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值