一、算法概述
-
问题定义:给定一个整数序列,找到其中最长的严格递增(递减)子序列的长度。子序列中的元素不需要连续,但必须保持原序列的顺序。例如,序列
[10, 9, 2, 5, 3, 7, 101, 18]的最长递增子序列是[2, 3, 7, 101],长度为 4。 -
应用场景:该算法在生物信息学(DNA序列分析)、数据压缩和计算机视觉等领域有广泛应用。
二、算法思路
-
动态规划思想:维护一个辅助数组
g,其中g[i]表示长度为i的递增子序列的最小可能末尾元素。通过贪心策略和二分查找优化,高效地更新该数组。 -
核心策略:对于每个元素,尝试将其插入或替换到辅助数组
g中的适当位置,以保持数组的单调性。插入位置由二分查找确定。 -
关键性质:
- 数组
g始终保持严格递增。 - 数组
g的长度即为当前找到的最长递增子序列的长度。
- 数组
三、伪代码实现
算法:最长递增子序列(贪心+二分优化)
输入:数组 a[0..n-1],数组长度 n
输出:最长递增子序列的长度
function getLIS(n, a[]):
// g[i] 表示长度为 i+1 的递增子序列的最小末尾元素
g[0..n-1]
gSize = 0 // 当前最长递增子序列的长度
for i = 0 to n-1:
// 二分查找插入位置
l = -1, r = gSize
while l + 1 < r:
mid = (l + r) / 2 // 向下取整
if g[mid] >= a[i]:
r = mid
else:
l = mid
// 插入或替换操作
if r == gSize:
g[gSize] = a[i]
gSize = gSize + 1
else:
g[r] = a[i]
return gSize
// 主程序
输入 n
输入数组 a[0..n-1]
输出 getLIS(n, a)
四、算法解释
-
初始化阶段:
- 创建辅助数组
g用于存储递增子序列的最小末尾元素。 gSize记录当前最长递增子序列的长度,初始为 0。
- 创建辅助数组
-
遍历处理每个元素:
- 二分查找:对于每个元素
a[i],在g中找到第一个大于或等于a[i]的位置r。 - 插入或替换:
- 如果
r等于当前gSize,说明a[i]可以扩展当前最长子序列,将其添加到g的末尾。 - 否则,将
g[r]替换为a[i],以减小长度为r+1的子序列的末尾元素,为后续扩展提供可能。
- 如果
- 二分查找:对于每个元素
-
示例演示:
- 输入数组
[3, 1, 2, 5, 4, 6]。 - 处理过程:
- 处理 3:
g = [3],长度 1。 - 处理 1:
g = [1],替换 3。 - 处理 2:
g = [1, 2],扩展长度。 - 处理 5:
g = [1, 2, 5]。 - 处理 4:
g = [1, 2, 4],替换 5。 - 处理 6:
g = [1, 2, 4, 6],最终长度为 4。
- 处理 3:
- 输入数组
-
正确性证明:
- 数组
g始终保持严格递增,确保二分查找的正确性。 - 通过替换操作,贪心策略保证了每个长度的子序列末尾元素尽可能小,从而允许后续元素更容易扩展。
- 数组
五、复杂度分析
- 时间复杂度: O ( n l o g n ) O(n log n) O(nlogn),其中 n n n 是数组长度。每个元素的处理需要 O ( l o g n ) O(log n) O(logn) 的二分查找。
- 空间复杂度:
O
(
n
)
O(n)
O(n),主要用于存储辅助数组
g。 - 与朴素 DP 的对比:传统动态规划方法的时间复杂度为 O ( n 2 ) O(n²) O(n2),而本算法通过贪心和二分优化将复杂度降低到 O ( n l o g n ) O(n log n) O(nlogn),适用于大规模数据。
本文为作者(英雄哪里出来)在抖音的独家课程《英雄C++入门到精通》、《英雄C语言入门到精通》、《英雄Python入门到精通》三个课程的配套文字讲解,如需了解算法视频课程,请移步 作者本人 的抖音直播间。
6万+

被折叠的 条评论
为什么被折叠?



