LeetCode 300. 最长递增子序列

72 篇文章 0 订阅
300. 最长递增子序列

难度 中等

给你一个整数数组 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)) 吗?

题解

​ 这道题以前有了解过,但是忘记的差不多了,只记得是动态规划的题目。然后看了以前收藏的博客,从新学习和实现了一遍。

​ 动态规划实现有两个方法,一种是O(n^2)的实现方法,另外一种是O(nlogn)的实现方法。

动态规划 O(n^2)

​ 那这里我们就需要弄清楚状态转移方程,状态是怎么进行转移的。

​ 如果 i > j,且nums[i] > nums[j],就是在 i 位置的数大于 j 位置的数,那么最长上升子序列长度加一,即dp[i] = dp[j] + 1。

例如10,9,2,5,3,7,101,18,假设变量到101时,此时的最长上升子序列为2,3,7,长度为3。又101大于7,所有此时最长上升子序列为2,3,7,101,长度为4。

​ 所以动态转移方程为if( i > j && nums[i] > nums[j]),dp[i] = dp[j] + 1

class Solution {
    public int lengthOfLIS(int[] nums) {
        int n = nums.length;//数组长度
        int[] dp = new int[n];//动态规划数组
        int ans = 1;//最短长度为1
        for(int i = 0; i < n; i++){
            dp[i] = 1;//初始化长度为1,每个元素都是一个最短的最长上升子序列
            for(int j = 0; j < i; j++){
                if(nums[j] < nums[i]){//如果满足 i > j && nums[i] > nums[j],更新动态规划数组
                    dp[i] = Math.max(dp[i], dp[j] + 1);
                }
            }
            ans = Math.max(ans, dp[i]);//每次都比较最长上升子序列的长度
        }
        return ans;
    }
}
动态规划 + 贪心 O(nlogn)

​ 为了尽可能求的更长的最长上升子序列,我们每次在最长上升子序列尾上升尽可能小的元素。那我们就需要借助一个数组来记录当前的最长上升子序列,然后我们每次遍历到的元素都放进去这个数组,替换最长上升子序列中对应位置的元素,讲的话比较抽象,就举个例子。

​ 10,9,2,5,3,7,101,18,当我们遍历到5的时候,最长上升子序列为2,5,然后又继续遍历。到了3,此时为了让后面能更长,此时选择3做为末尾是最佳的(贪心,选最小的)。

​ 另外,我们替换元素时,需要查找到元素在最长上升序列的哪个位置,因为我们是最长上升子序列,这个序列是有序的,我们可以利用有序数组进行二分查找,提高查找效率,将查找时间从O(n)降为O(logn)

class Solution {
    public int lengthOfLIS(int[] nums) {
        int[] low = new int[nums.length + 1];//辅助数组记录最长上升子序列
        int ans = 1;
        low[ans] = nums[0];//初始化
        for(int i = 1; i < nums.length; i++){
            if(nums[i] > low[ans]){//如果第i个元素大于末尾元素,直接插入末尾,然后指针右移
                low[++ans] = nums[i];
            }else{//如果小于末尾元素,需要插入有序数组内部,利用有序数组二分查找位置
                low[bin_search(low, ans, nums[i])] = nums[i];
            }
        }
        return ans;
    }

    int bin_search(int[] a, int r, int x){//二分查找
        int l = 1;
        while(l <= r){
            int mid = l + (r - l) / 2;
            if(a[mid] < x){
                l = mid + 1;
            }else{
                r = mid - 1;
            }
        }
        return l;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值