文章目录

  • 1. 题目
    • 1.1 示例
    • 1.2 说明
    • 1.3 提示
    • 1.4 进阶
  • 2. 解法一(动态规划)
    • 2.1 分析
    • 2.2 解答
    • 2.3 复杂度
  • 3. 解法二(二分查找)
    • 3.1 分析
    • 3.2 解答
    • 3.3 复杂度
  • 4. 进阶(输出最长递增子序列)

1. 题目

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

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

1.1 示例

  • 示例 1:
  • 输入: nums = [10, 9, 2, 5, 3, 7, 101, 18]
  • 输出: 444
  • 解释: 最长递增子序列是 [2, 3, 7, 101],因此长度为 444 。
  • 示例 2:
  • 输入: nums = [0, 1, 0, 3, 2, 3]
  • 输出: 444
  • 示例 3:
  • 输入: nums = [7, 7, 7, 7, 7, 7, 7]
  • 输出: 111

1.2 说明

  • 来源: 力扣(LeetCode)
  • 链接: https://leetcode-cn.com/problems/longest-increasing-subsequence

1.3 提示

  • 1≤nums.length≤25001 \le nums.length \le 25001≤nums.length≤2500
  • −104≤nums[i]≤104-10^4 \le nums[i] \le 10^4−104≤nums[i]≤104

1.4 进阶

  • 你可以设计时间复杂度为 O(n2)O(n^2)O(n2) 的解决方案吗?
  • 你能将算法的时间复杂度降低到 O(nlog(n))O(n log(n))O(nlog(n)) 吗?
  • 你能进一步输出该最长递增子序列吗?

2. 解法一(动态规划)

2.1 分析

定义 dp[i] 为 以 nums[i] 结尾的最长递增子序列长度。根据这个定义,很显然 dp[i] 的初始值都应该为 111 ,因为以 nums[i] 结尾的最长递增子序列至少应该包含其自身。

接下来我们从小到大计算 dp 数组的值,假设在计算 dp[i] 之前,我们已经计算出 dp[0]dp[i − 1] 的值,可以据此根据下列公式(即状态转移方程)计算出 dp[i]

dp[i]=max(dp[j])+1dp[i] = max(dp[j]) + 1 dp[i]=max(dp[j])+1

其中: 0≤j<i0≤j<i0≤j<i 且 num[j] < num[i] ,即尝试往 dp[0]dp[i − 1] 中所对应的最长递增子序列后面再加一个 nums[i] 。由于 dp[j] 代表以 nums[j] 结尾的最长递增子序列,所以如果能从 dp[j] 这个状态转移过来,那么 nums[i] 必然要大于 nums[j],才能将 nums[i] 放在 nums[j] 后面以形成更长的递增子序列。

最后,我们所需的结果一定是 dp 数组中的最大值。

  • 作者: LeetCode-Solution

2.2 解答

from typing import Listclass Solution:@classmethoddef length_of_lis(cls, nums: List[int]) -> int:if not nums:return 0dp = [1 for _ in range(len(nums))]for i in range(len(nums)):for j in range(i):if nums[i] > nums[j]:dp[i] = max(dp[i], dp[j] + 1)return max(dp)def main():nums = [10, 9, 2, 5, 3, 7, 101, 18]sln = Solution()print(sln.length_of_lis(nums))if __name__ == '__main__':main()
  • 执行用时: 2928 ms , 在所有 Python3 提交中击败了 40.73% 的用户;
  • 内存消耗: 15.2 MB , 在所有 Python3 提交中击败了 29.34% 的用户。

2.3 复杂度

  • 时间复杂度: O(n2)O(n^2)O(n2),其中 nnn 为数组 nums 的长度。动态规划的状态数为 nnn,计算状态 dp[i] 时,需要 O(n)O(n)O(n) 的时间遍历 dp[0]dp[i − 1] 的所有状态,所以总时间复杂度为 O(n2)O(n^2)O(n2);
  • 空间复杂度: O(n)O(n)O(n),需要额外使用长度为 nnn 的 dp 数组。

3. 解法二(二分查找)

  • 作者: hiepit

3.1 分析

  • 假设 nums = [2, 6, 8, 3, 4, 5, 1] ,按照下列步骤使用初始为空的辅助序列可以得到最长递增子序列的长度:

    1. 选取第一个元素 222 ,则 sub1 = [2]
    2. 由于 666 大于前一个元素,那么 sub1 = [2, 6]
    3. 由于 888 大于前一个元素,那么 sub1 = [2, 6, 8]
    4. 由于 333 小于前一个元素,我们不能直接将其追加至 sub1 之后,但这个元素必须暂时保留,因为在之后可能会有以 [2, 3] 开头的最长子序列,所以这里再使用另外一个辅助序列,最后得到 sub1 = [2, 6, 8]sub2 = [2, 3]
    5. 对于元素 444 ,我们虽不能将其追加至 sub1 之后,但可以追加至 sub2 之后,因此 sub1 = [2, 6, 8]sub2 = [2, 3, 4]
    6. 对于元素 555 ,类似地有 sub1 = [2, 6, 8]sub2 = [2, 3, 4, 5]
    7. 对于元素 111 ,类似第 444 步,有 sub1 = [2, 6, 8]sub2 = [2, 3, 4, 5]sub3 = [1]
    8. 最后,我们得到最长的递增子序列长度的为 len(sub2) = 4
  • 然而,在上述步骤中,由于我们需要使用多个辅助的序列,因此效率会很低。实际上,我们可以仅使用一个辅助序列 sub
    1. 当nums[i] 大于 sub 中最后一个元素时,直接往其后进行元素追加;
    2. 当某元素 nums[i] 不大于 sub 中的最后一个元素时,我们可以使用二分查找先找到 sub 中不小于 nums[i] 的元素中最小的那个,然后使用 nums[i] 对其进行替换。
  • 基于上述改进,我们还是以 nums = [2, 6, 8, 3, 4, 5, 1] 为例,解释该算法的主要过程:
    1. 对于元素 222 ,有 sub = [2]
    2. 对于元素 666 ,有 sub = [2, 6]
    3. 对于元素 888 ,有 sub = [2, 6, 8]
    4. 对于元素 333 ,此时需要先找到 sub 中不小于 333 的最小元素,即 666 ;然后替换后得到 sub = [2, 3, 8]
    5. 对于元素 444 ,同样,将其替换 888 ,得到 sub = [2, 3, 4]
    6. 对于元素 555 ,有 sub = [2, 3, 4, 5]
    7. 对于元素 111 ,同第 444 和第 555 步,有 sub = [1, 3, 4, 5]
    8. 最终,可得最长递增子序列的长度为 len(sub) = 4
  • 实际上,上述步骤最后得到的 sub 相当于按照 Patience Sorting 进行排列扑克牌的分堆后,每一堆牌最顶层牌组成的序列。

3.2 解答

from bisect import bisect_left
from typing import Listclass BinaryGreedySolution:@classmethoddef length_of_lis(cls, nums: List[int]) -> int:sub = []for num in nums:if len(sub) == 0 or num > sub[-1]:sub.append(num)else:idx = bisect_left(sub, num)sub[idx] = numreturn len(sub)def main():nums = [10, 9, 2, 5, 3, 7, 101, 18]print(BinaryGreedySolution.length_of_lis(nums))if __name__ == '__main__':main()
  • 执行用时: 40 ms , 在所有 Python3 提交中击败了 96.69% 的用户;
  • 内存消耗: 15 MB , 在所有 Python3 提交中击败了 87.45% 的用户。

3.3 复杂度

  • 时间复杂度: O(nlog(n))O(nlog(n))O(nlog(n)) ,其中 nnn 为序列 nums 中的元素数量;
  • 空间复杂度: O(n)O(n)O(n) ,使用了一个辅助的序列,该序列最多保存 nnn 个元素。

4. 进阶(输出最长递增子序列)

from bisect import bisect_left
from typing import Listclass BinaryGreedySolution:@classmethoddef length_of_lis(cls, nums: List[int]) -> int:sub = []for num in nums:if len(sub) == 0 or num > sub[-1]:sub.append(num)else:idx = bisect_left(sub, num)sub[idx] = numreturn len(sub)@classmethoddef path_of_lis(cls, nums: List[int]):sub = []sub_index = []  # Store index instead of value for tracing path purposepath = [-1] * len(nums)  # path[i] point to the index of previous number in LISfor i, x in enumerate(nums):if len(sub) == 0 or sub[-1] < x:if len(sub_index) == 0:path[i] = -1else:path[i] = sub_index[-1]sub.append(x)sub_index.append(i)else:idx = bisect_left(sub, x)  # Find the index of the smallest number >= x, replace that number with xif idx == 0:path[i] = -1else:path[i] = sub_index[idx - 1]sub[idx] = xsub_index[idx] = ians = []t = sub_index[-1]while t >= 0:ans.append(nums[t])t = path[t]return ans[::-1]def main():nums = [10, 9, 2, 5, 3, 7, 101, 18]print(BinaryGreedySolution.length_of_lis(nums))print(BinaryGreedySolution.path_of_lis(nums))if __name__ == '__main__':main()
  • 时间复杂度: O(nlogn)O(nlogn)O(nlogn)
  • 空间复杂度: O(n)O(n)O(n)

【LeetCode 二分查找专项】最长递增子序列(300)(to be polished...)相关推荐

  1. Vue3 最长递增子序列详解

    Vue3 最长递增子序列研究 本文初衷 彻底讲清楚 Vue3 源码中实现最长递增子序列的算法. 概念名词 **最长递增子序列:**在一个给定的数值序列中,找到一个子序列,使得这个子序列元素的数值依次递 ...

  2. 动态规划(最长递增子序列)---最长递增子序列

    最长递增子序列 300. Longest Increasing Subsequence (Medium) 题目描述:   给定一个数组,找到它的最长递增子序列 思路分析:   动态规划思想,定义一个数 ...

  3. 【LeetCode 动态规划专项】最长递增子序列的个数(673)

    文章目录 1. 题目 1.1 示例 1.2 说明 1.3 提示 1.4 进阶 2. 解法一(动态规划) 2.1 分析 2.1.1 定义状态 2.1.2 初始化状态 2.1.3 状态转移 2.1.4 返 ...

  4. 动态规划应用--最长递增子序列 LeetCode 300

    文章目录 1. 问题描述 2. 解题思路 2.1 动态规划 2.2 二分查找 1. 问题描述 有一个数字序列包含n个不同的数字,如何求出这个序列中的最长递增子序列长度?比如2,9,3,6,5,1,7这 ...

  5. leetcode(300)—— Longest Increasing Subsequence(最长递增子序列)

    参考 Python 解法: 动态规划 -- 最长递增子序列(LIS) 原题位置:Longest Increasing Subsequence | LeetCode OJ 题目的说明: 严格递增: 子序 ...

  6. LeetCode高频题300. 最长递增子序列

    LeetCode高频题300. 最长递增子序列 提示:本题是系列LeetCode的150道高频题,你未来遇到的互联网大厂的笔试和面试考题,基本都是从这上面改编而来的题目 互联网大厂们在公司养了一大批A ...

  7. 【Leetcode】最长递增子序列问题及应用

    文章目录 最长递增子序列问题及应用 300. 最长递增子序列 面试题 17.08. 马戏团人塔 354. 俄罗斯套娃信封问题 面试题 08.13. 堆箱子 1691. 堆叠长方体的最大高度 406. ...

  8. c语言最长递增子序列nlogn,十月常见算法考题、最长递增子序列,Leetcode第300题最长上升子...

    十月常见算法考题.最长递增子序列,Leetcode第300题最长上升子 十月常见算法考题.最长递增子序列,Leetcode第300题最长上升子序列的变种,我没见过乔丹,今天詹姆斯就是我的神! @Aut ...

  9. 十月常见算法考题、最长递增子序列,Leetcode第300题最长上升子序列的变种,我没见过乔丹,今天詹姆斯就是我的神!

    @Author:Runsen @Date:2020/10/12 十月过得很平缓.在这个"收获的季节",我成了为数不多不必收获的人.每天睡到中午,即使闹钟设在早上也很难把自己弄醒. ...

最新文章

  1. 第四范式联合浪潮商用机器发布AI一体机,接入AI像使用手机一样简单
  2. 突破高连接性能瓶颈,图数据库在银行业这么用
  3. yourtour的几种链接
  4. 动画代码Android动画学习笔记动画代码
  5. Python的配置文件模块yaml的使用
  6. 第二章 马尔科夫决策过程和贝尔曼等式-强化学习理论学习与代码实现(强化学习导论第二版)
  7. 统计细菌基因组ORF
  8. 充电和库仑计,charge(bq24161) and coulomb(bq27425)
  9. 智慧家居·万物互联:我的智能花盆DIY之旅(ESP32)
  10. 苹果手机声音突然变小是怎么回事_苹果手机听筒声音小怎么回事?
  11. 出版印刷纸张大小尺寸一览表
  12. Mysql 主从复制的作用和原理
  13. STM32C8T6 流水灯的实现(库函数版)
  14. 微信小程序开发中调用button组件添加微信客服功能
  15. nolo手柄配对不上_NOLO手柄助手下载
  16. a4纸对折c语言,一张A4纸最多可对折几次,这个实验太好玩了
  17. 游戏美术3D建模次世代制作流程图文详解
  18. 汇编,CFA,CFI
  19. Python:看我如何全程自动玩游戏带你飞,直接无敌
  20. 简单计算器程序 (实现加减乘除基本运算)

热门文章

  1. JavaEE项目bug修复记——一场由特殊空字符(160号ASCII码)引发的血案
  2. 数字联接新动能 | 专访亿联IT总监赖志豪:AI是亿联未来数字化建设的方向
  3. 通过ONIE安装NOS系统
  4. Is Sampling Heuristics Necessary in Training Deep Object Detectors? 论文笔记
  5. 【软考 系统架构设计师】软件工程⑩ 系统运行与软件维护
  6. (47)【漏洞发现】漏扫工具合集、WAF绕过分类
  7. GoLang之使用sync.pool和sync.cond
  8. vfio概述(vfio/iommu/device passthrough)
  9. bzoj3083 遥远的国度 bzoj3626 LCA (树链剖分)
  10. R语言实战-第九章 R in action-chapter9