最长递增子序列

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)) 吗?

一.动态规划初步:

def lengthOfLIS(nums: List[int]) -> int:
    # 存储以每个位置结尾的最长递增子序列长度
    dp = [1] * len(nums)
    for i in range(1, len(nums)):
        # 遍历前面的位置
        for j in range(i):
            # 如果当前数大于前面的数,更新当前位置的最长递增子序列长度
            if nums[i] > nums[j]:
                dp[i] = max(dp[i], dp[j] + 1)
    return max(dp)  # 返回最长递增子序列的长度

讲解

  • dp 数组用于记录以每个位置结尾的最长递增子序列的长度,初始都为 1。
  • 通过两个嵌套的循环,对于每个位置 i,检查前面的位置 j,如果满足递增条件,则尝试更新 i 位置的长度。
  • 最后返回 dp 数组中的最大值即为最长递增子序列的长度。

假设数组为 [10,9,2,5,3,7,101,18]

位置dp 值(初始)dp 值(计算过程)
011
111
211
312(因为 5 大于 2)
412(因为 3 大于 2 但不大于 5)
513(因为 7 大于 3 和 5)
614(因为 101 大于前面的所有数)
714(因为 18 小于 101)
位置 / 阶段1234567
01------
111(9 小于 10)-----
211(2 小于 9)1(2 小于 10)----
312(5 大于 2)2(5 大于 9 不成立)2(5 大于 10 不成立)---
412(3 大于 2)2(3 大于 9 不成立)2(3 大于 10 不成立)2(3 大于 5 不成立)--
512(7 大于 2)3(7 大于 5)3(7 大于 10 不成立)3(7 大于 3 成立)3(7 大于 9 不成立)-
613(101 大于 2)3(101 大于 5)4(101 大于 7)4(101 大于 3)4(101 大于 9)4(101 大于 10)
713(18 小于 101)3(18 小于 5)3(18 小于 7)3(18 小于 3)3(18 小于 9)4(18 大于 10 不成立)

二. 贪心结合二分查找:

将时间复杂度降低到O(n log(n)) 

代码如下:

def lengthOfLIS(nums: List[int]) -> int:
    tails = [0] * len(nums)  # 存储递增序列的尾部元素
    size = 0  # 当前递增序列的长度

    for num in nums:
        # 二分查找插入位置
        i, j = 0, size
        while i < j:
            m = (i + j) // 2  # 计算中间位置
            if tails[m] < num:  # 如果中间位置元素小于当前元素
                i = m + 1  # 左指针移动到中间后一位
            else:
                j = m  # 右指针就是中间位置
        tails[i] = num  # 更新尾部元素
        if i == size:  # 如果插入位置等于当前长度
            size += 1  # 递增序列长度加 1
    return size

讲解

  • tails 数组用于维护一个递增的序列。
  • 对于每个新的数字,通过二分查找找到它在 tails 中应该插入的位置。如果插入到最后,说明递增序列长度增加了。
  • 这样可以在  的时间复杂度内找到最长递增子序列的长度。这种方法利用了贪心的思想,尽量保持较小的尾部元素,以便能容纳更多的数字。

  • tails = [0] * len(nums):创建一个与输入数组等长的列表 tails,用于存储可能的递增序列的尾部元素。
  • size = 0:表示当前已经确定的递增序列的实际长度。

然后遍历输入数组中的每个元素 num

  • 通过二分查找找到 num 应该插入到 tails 中的位置。在二分查找过程中,不断调整左右边界,找到合适的中间位置 m,如果 tails[m] < num,说明应该在右边继续找,否则就在左边找。
  • 找到插入位置 i 后,将 num 更新到 tails[i]
  • 如果插入位置 i 正好等于当前的长度 size,说明 num 拓展了递增序列的长度,就将 size 加 1。

这个过程中,实际上是在不断维护一个有序的尾部元素列表,每次新元素加入时,都通过二分查找找到合适的位置插入,以保证这个列表始终代表着最长递增子序列的尾部情况。这样最终得到的 size 就是最长递增子序列的长度。

这种方法巧妙地利用了贪心策略和二分查找来高效地求解问题。

  • 12
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值