尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

最长递增子序列

最长递增子序列
📅 发布时间:2026/6/19 9:45:02

https://leetcode.cn/problems/longest-increasing-subsequence/ 快手一面手撕算法

我们这篇文章的解法,结合了贪心思想 + 二分查找,非常精妙

算法解析


🎯 问题回顾

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

例如:nums = [10,9,2,5,3,7,101,18]
最长递增子序列可以是[2,3,7,18]或[2,3,7,101],长度为4。

注意:子序列 ≠ 子数组(不要求连续)。


💡 核心思想:“用更小的尾巴,争取更长的未来”

我们不关心具体的 LIS 是什么,只关心它的长度。于是,我们可以维护一个数组tails,其含义是:

tails[i]表示:所有长度为i+1的递增子序列中,末尾元素的最小值。

✅ 为什么维护“最小末尾”?

  • 末尾越小,后面就越容易接上更大的数,从而延长子序列。
  • 这是一种贪心策略:在保证长度的前提下,让结尾尽可能小。

🔍 举个例子,手动模拟

输入:nums = [10, 9, 2, 5, 3, 7, 101, 18]

我们逐个处理每个数字x,并更新tails:

xtails(处理前)操作tails(处理后)说明
10[]空,直接 append[10]长度1的LIS最小末尾是10
9[10]找第一个 ≥9 的位置 → index=0[9]用9替换10,更小的末尾更好
2[9]找 ≥2 → index=0[2]继续优化长度1的末尾
5[2]5 > 2 → 可延长[2,5]现在有长度2的LIS,末尾5
3[2,5]找 ≥3 → index=1(5≥3)[2,3]长度2的末尾从5→3,更优
7[2,3]7 > 3 → 延长[2,3,7]出现长度3
101[2,3,7]101>7 → 延长[2,3,7,101]长度4
18[2,3,7,101]找 ≥18 → index=3(101≥18)[2,3,7,18]长度4的末尾优化为18

✅ 最终len(tails) = 4,就是答案!

📌 注意:tails本身不一定是真实的 LIS(比如中间过程[2,3,7,18]是真的,但有些情况不是),但它长度一定等于 LIS 长度!


🔧 算法步骤详解

from bisect import bisect_left def lengthOfLIS(self, nums: List[int]) -> int: tails = [] # 初始化空数组 for x in nums: # 在 tails 中找第一个 >= x 的位置 pos = bisect_left(tails, x) if pos == len(tails): # x 比所有 tails 元素都大 → 可以延长 LIS tails.append(x) else: # 替换 tails[pos] 为 x,使该长度的末尾更小 tails[pos] = x return len(tails)

关键点解析:

1.bisect_left(tails, x)的作用
  • 因为tails始终保持严格递增(证明见下文),所以可以用二分查找。
  • bisect_left返回第一个≥ x的索引。
2.为什么tails是递增的?
  • 假设存在i < j但tails[i] ≥ tails[j]。
  • 那么长度为j+1的 LIS 末尾是tails[j],而tails[j] ≤ tails[i]。
  • 但我们完全可以从这个长度为j+1的序列中取前i+1个元素,得到一个长度为i+1、末尾 ≤tails[j] ≤ tails[i]的序列 —— 这与tails[i]是“长度 i+1 的最小末尾”矛盾。
  • ✅ 所以tails必然严格递增!

正因为tails有序,才能用bisect_left!

3.替换操作不会影响已有的 LIS 长度
  • 替换只是优化未来可能性,不改变当前最大长度。
  • 例如:[2,5]→[2,3],长度仍是2,但后续遇到4时,就能组成[2,3,4],而[2,5]就不行。

⏱️ 复杂度分析

  • 时间复杂度:

    • 遍历n个元素 → O(n)
    • 每次二分查找 → O(log L),L 是当前 LIS 长度(≤ n)
    • 总计:O(n log n)
  • 空间复杂度:

    • tails最多存 n 个元素 →O(n)

✅ 这是目前已知的 LIS 问题最优解法(仅求长度时)。


❗ 常见误区澄清

误区正确理解
“tails就是最长递增子序列”❌ 不一定!它只保证长度正确,内容可能不是真实子序列
“只能用于严格递增”✅ 是的,本题是严格递增。若允许非严格(≤),需改用bisect_right
“不能还原具体 LIS”✅ 正确!此方法只求长度。若要输出具体序列,需用 O(n²) DP + 回溯

🧪 测试用例验证

nums = [0,1,0,3,2,3] → tails 变化: 0: [0] 1: [0,1] 0: [0,1] → 替换0? 不,bisect_left([0,1], 0)=0 → tails[0]=0(不变) 3: [0,1,3] 2: [0,1,2] 3: [0,1,2,3] → 长度4 ✅

✅ 总结:一句话记住这个算法

“维护一个递增的‘尾巴数组’,每次用新数字要么延长它,要么优化某个位置的尾巴,使其更小——这样未来的增长空间更大。”

这既是贪心(局部最优),又借助二分(高效查找),是算法设计中的经典范式。

跟着例子学算法

输入:nums = [10, 9, 2, 5, 3, 7, 101, 18]

from bisect import bisect_left from typing import List class Solution: def lengthOfLIS(self, nums: List[int]) -> int: tails = [] # tails[i] 表示长度为 i+1 的递增子序列的最小末尾 for x in nums: pos = bisect_left(tails, x) # 👈 关键:找插入位置 if pos == len(tails): tails.append(x) # 👈 能延长 LIS else: tails[pos] = x # 👈 优化某个长度的末尾 return len(tails)

下面,我们逐行、逐元素地解释代码在每一步做了什么,并对应到你表格中的状态变化。


🔹 初始状态

tails = []
  • tails为空,表示还没有任何递增子序列。

🔸 第1步:x = 10

pos = bisect_left(tails, 10) # tails=[], 所以 pos = 0 if pos == len(tails): # 0 == 0 → True tails.append(10) # tails = [10]

✅作用:第一个数,直接作为长度为1的LIS的末尾。
📊tails = [10]


🔸 第2步:x = 9

pos = bisect_left([10], 9) # 在 [10] 中找 ≥9 的位置 → index=0 if 0 == len([10])? → 0 == 1? → False else: tails[0] = 9 # tails = [9]

✅作用:9 比 10 小,不能延长(因为 9 < 10,但我们要严格递增,且当前最长只有1),但可以用 9替代长度为1的LIS的末尾,使其更小,为未来留空间。
📊tails = [9]


🔸 第3步:x = 2

pos = bisect_left([9], 2) # 2 < 9 → pos = 0 0 != 1 → 进入 else tails[0] = 2 # tails = [2]

✅作用:继续优化“长度为1”的LIS末尾,从9降到2,更小更好!
📊tails = [2]


🔸 第4步:x = 5

pos = bisect_left([2], 5) # 5 > 2 → 所有元素 < 5 → pos = len(tails) = 1 if 1 == 1 → True tails.append(5) # tails = [2, 5]

✅作用:5 比当前所有末尾都大(比2大),说明可以接在[2]后面,形成长度为2的递增子序列[2,5]。
📊tails = [2, 5]


🔸 第5步:x = 3

pos = bisect_left([2,5], 3) # # 2 < 3 → 继续;5 ≥ 3 → 停在 index=1 pos = 1 len(tails)=2 → 1 != 2 → else tails[1] = 3 # tails = [2, 3]

✅作用:3 不能延长到长度3(因为 3 < 5),但它可以替代长度为2的LIS的末尾:

  • 原来长度2的末尾是5(如[2,5])
  • 现在可以用[2,3],末尾更小,以后遇到4就能接上!
    📊tails = [2, 3]

🔸 第6步:x = 7

pos = bisect_left([2,3], 7) # 7 > 3 → pos = 2 len(tails)=2 → 2 == 2 → True tails.append(7) # tails = [2, 3, 7]

✅作用:7 比所有末尾都大,可以接在[2,3]后,形成长度为3的LIS[2,3,7]。
📊tails = [2, 3, 7]


🔸 第7步:x = 101

pos = bisect_left([2,3,7], 101) # 101 > 7 → pos = 3 3 == len(tails)=3 → True tails.append(101) # tails = [2, 3, 7, 101]

✅作用:继续延长,得到长度为4的LIS。
📊tails = [2, 3, 7, 101]


🔸 第8步:x = 18

pos = bisect_left([2,3,7,101], 18) # 2<18, 3<18, 7<18, 101≥18 → pos = 3 len(tails)=4 → 3 != 4 → else tails[3] = 18 # tails = [2, 3, 7, 18]

✅作用:18 不能形成长度5(因为 18 < 101,但前面最大末尾是101,而18比它小),
但它可以优化长度为4的LIS的末尾:

  • 原来是[2,3,7,101]
  • 现在可以是[2,3,7,18],末尾更小,如果后面有19、20,就能继续延长!
    📊tails = [2, 3, 7, 18]

🎯 最终结果

return len(tails) # 4

✅ 返回4,正是 LIS 的长度。


🔑 核心代码逻辑总结

代码行作用
pos = bisect_left(tails, x)在已有的“最优末尾”中,找到第一个 ≥ x 的位置
if pos == len(tails):说明 x 比所有末尾都大 → 可以延长LIS
tails.append(x)新增一个更长的长度
else: tails[pos] = x用 x替换某个长度的末尾,使其更小(贪心优化)

💡 为什么这样是对的?

  • tails始终保持严格递增(可数学归纳法证明)
  • 每次操作都不减少 LIS 长度,只可能增加或优化
  • 最终len(tails)就是最长可能的长度

✅ 总结一句话

代码通过维护一个“各长度下最小末尾”的有序数组tails,利用二分查找高效决定:是延长序列,还是优化已有长度的结尾——从而在 O(n log n) 时间内求出 LIS 长度。

相关新闻

  • 【微信支付全流程实战:JSAPI+H5 支付对接指南】
  • 2025年年终透水砖厂家推荐:聚焦海绵城市建设需求,专家严选5家优质厂家核心能力与性价比横评 - 十大品牌推荐
  • 从零开始学大模型RL训练框架:收藏这篇就够了!

最新新闻

  • 2026随州黄金回收白银回收铂金回收门店实测|本地正规实体老店无套路门店推荐 - 中安检金银铂钻回收
  • 2026 无锡无套路黄金回收商家白名单排行:线上预估价等同到手价门店汇总 - 开心测评
  • STM32CubeMX实战指南:FreeRTOS消息队列在任务间高效通信的设计与实现
  • 面试官坏笑:“你用 AI 编程半年了,那怎么保证 Claude Code 写出来的代码是对的?”我:“直接用 Claude Opus 4.8!”
  • 广州海珠区金价高位运行,市民上门变现正当时 - 上门黄金回收
  • 合肥市管道漏水检测,室外地埋消防市政主管网漏水检测一站式服务 - 同城资讯

日新闻

  • 5分钟掌握Python进化算法:Geatpy高性能优化工具完全指南
  • Microchip 24AA044 EEPROM选型与应用全指南:从参数解析到实战编程
  • 华为的鸿蒙到底有多牛?为什么称作遥遥领先?

周新闻

  • 3步解锁iOS设备:applera1n激活锁绕过完全指南
  • 39 2026 人工智能证书终极盘点,普通人选 AI 证书可以从这些方向入手
  • Redis 暴露公网有多危险?从端口检查到补救步骤

月新闻

  • 【总结】入门篇:50句话让你记住架构核心概念
  • WeChatMsg技术方案解析:实现Mac微信数据自主管理的完整解决方案
  • WeChatMsg:革新性微信数据备份方案,打造你的专属数字记忆库

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号