力扣小白刷题之300题最长上升子序列

题目描述

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

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

思路

动态规划

  1. 定义状态
  • dp[i] 表示 以 nums[i] 为结尾的上升子序列的长度。注意:nums[i] 必须选取且必须是这个子序列的最后一个元素。
  1. 考虑状态转移方程
  • 遍历到 nums[i] 时,需要把下标 i 之前的所有的数都看一遍
  • 只要 nums[i] 严格大于在它位置之前的某个数,那么 nums[i] 就可以接在这个数后面形成一个更长的上升子序列
  • 因此,dp[i] 就等于 下标 i 之前 严格小于 nums[i] 的状态值的最大者 + 1
    在这里插入图片描述
  1. 初始化
  • dp[i] = 1,一个字符是长度为1的上升子序列(每一个数都有可能是最长子序列的起点)
  1. 考虑输出
  • 不能返回最后一个状态值,最后一个状态值只是以 nums[n - 1] 结尾的 上升子序列的长度。
  • 需要返回 dp数组中的最大值。
  1. 考虑状态压缩
  • 遍历到一个新数的时候,之前所有的状态值都得保留,因此无法压缩。

时间复杂度:O(N^2),N是数组长度,这里使用了两个 for 循环,每个for循环的时间复杂度都是线性的。
空间复杂度:O(N),要使用和输入数组长度相等的状态数组。

代码

在这里插入图片描述

优化 (时间复杂度为O(NlogN))

同时使用二分查找 和 贪心算法

  • 依旧是着眼于一个上升子序列的结尾的元素
  • 如果已经得到的一个上升子序列的结尾的数越小,遍历的时候后面接上一个数,就会有更大的可能性构成一个更长的上升子序列
  • 既然结尾的数越小越好,我们可以记录在长度固定的情况下,结尾最小的那个元素的数值,这样定义也是为了方便得到【状态转移方程】。
  1. 定义新状态
  • tail[i] 表示长度为 i + 1 的所有上升子序列的结尾元素的最小值
    tail[0] 表示长度为 1 的所有上升子序列中,结尾最小的那个元素的数值
    tail[1] 表示长度为 2 的所有上升子序列中,结尾最小的那个元素的数值
  • 下标和长度有一个 1 的偏差
  1. 思考状态转移方程
    证明数组 tail[i] 也是一个严格上升数组
    **证明:**即对于任意的下标 0 <= i < j < len, 都有tail[i] < tail[j]
    在这里插入图片描述

我们只需要维护状态数组 tail 的定义,它的长度就是最长上升子序列的长度
下面说明如何在遍历过程中维护状态数组 tail 的定义:

  • 算法的执行流程:
    1. 设置一个数组 tail,初始时为空;
    2. 在遍历数组 nums 的过程中,每来一个新数 num,如果这个数 严格大于 有序数组 tail 的最后一个元素,就把 num 放到有序数组 tail 的后面,否则进入第 3 点;
    3. 在有序数组 tail 中查找第 1 个等于大于 num 的那个数,试图让它变小:
      • 如果有序数组 tail 中存在等于 num 的元素,什么都不做,因为以 num 结尾的最短的 上升子序列 已经存在
      • 如果有序数组 tail 中存在大于 num 的元素,找到第 1 个,让它变小,这样我们就找到了一个 结尾更小相同长度的上升子序列。
      • 这一步可以认为是贪心算法,总是做出在当前看来最好的选择,当前最好的选择是:当前只让第一个严格大于 nums[i] 的数变小,变成 nums[i] ,这一步操作是“无后效性”的。
    4. 遍历新的数 num,先尝试第 2 点,不通再执行 3,直到遍历完整个 nums 数组,**最终有序数组 tail 的长度,就是所求的“最长上升子序列”的长度。
    5. 特别的,因为 tail 数组有序,所以查找 nums[i] 位于 tail 数组的位置时,可以使用二分查找
  1. 思考初始化
    • tail[0] = nums[0],在只有一个 1 个元素的情况下,它当然是长度为 1 且结尾最小的元素。
  2. 思考输出
    • 数组 tail 的长度。依据定义,tail[i] 表示长度固定为 i + 1的所有上升子序列的结尾元素中最小的那个,长度最多就是数组 tail 的长度。
  3. 思考状态压缩
    无法压缩

代码

在这里插入图片描述
时间复杂度: O(N log N),遍历数组O(N),二分查找O(log N)
空间复杂度: O(N),开辟有序数组 tail的空间至多和原始数组一样多。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值