代码
时间复杂度为 O(N log N)
import bisect
n = int(input())
a = list(map(int, input().split()))
tails = []
for item in a:
pos = bisect.bisect_left(tails, item)
if pos == len(tails):
tails.append(item)
else:
tails[pos] = item
print(len(tails))
测试用例如下:
输入
6
1 4 2 2 5 6
输出
4
代码分析
这段代码旨在找到给定整数序列中的最长上升子序列(Longest Increasing Subsequence,LIS)。这种求解方法结合了动态规划思路和二分查找的高效性。
-
导入
bisect
模块:bisect
模块提供了对有序数组的二分查找功能,使得所需插入位置的查找能够在 O(log N) 的时间复杂度内完成。
-
读取输入:
- 从输入中读取一个整数
n
,它表示序列的长度。 - 然后读取
n
个整数,将它们存储在一个列表a
中,这些整数可以代表任何需要处理的数字,例如战力值、学生成绩等。
- 从输入中读取一个整数
-
初始化
tails
列表:tails
是一个用于存储当前找到的上升子序列尾部元素的列表。每个位置的元素都保持着非严格递增的顺序。这个数组的长度即为最长上升子序列的长度。
-
寻找最长上升子序列:
- 对于列表
a
中的每一个元素item
:- 使用
bisect.bisect_left(tails, item)
来查找item
在tails
中应该插入的位置pos
,这个位置是tails
中第一个大于或等于item
的索引。 - 如果
pos
等于tails
的长度,这意味着item
大于tails
中的任何元素,因此可以将item
直接追加到tails
中。 - 如果
pos
小于tails
的长度,说明item
可以替代该位置的元素,这样做的目的是为了保持tails
中的值尽可能小,以便为下一个可能的上升子序列保留空间。
- 使用
- 对于列表
-
输出结果:
- 最终,打印
tails
的长度,表示找到的最长上升子序列的长度。
- 最终,打印
理论
此算法的核心在于利用 tails
列表动态地维护当前可以构成的上升子序列的尾部。通过二分查找方法能够快速定位插入位置,从而保证在 O(log N) 的时间复杂度内完成对 tails
的更新。整体的逻辑简单而高效,相较于传统 O(N^2) 的动态规划方法,显著提高了运行效率,特别适合于处理大规模输入数据。
时间复杂度
- 时间复杂度:O(N log N)
- 主要由两部分组成:遍历每个输入元素(N)和在每次迭代中执行的二分查找(O(log N))。
空间复杂度
- 空间复杂度:O(N)
- 最坏情况下,
tails
中可能包含与输入长度相同的元素(即输入序列完全递增时),因此其空间复杂度为 O(N)。其他用来辅助存储的变量占用常数空间。
- 最坏情况下,
示例
假设输入的是一组数字,例如 [5, 3, 4, 8, 6, 7]
。使用该代码计算最长上升子序列的长度:
- 初始化:
tails
列表为空。 - 输入处理:
- 对于
5
,tails
变成[5]
。 - 对于
3
,tails
变为[3]
(替换5
)。 - 对于
4
,tails
变为[3, 4]
。 - 对于
8
,tails
变为[3, 4, 8]
。 - 对于
6
,tails
变为[3, 4, 6]
(替换8
)。 - 对于
7
,tails
变为[3, 4, 6, 7]
。
- 对于
- 输出:最终
tails
的长度为 4,表示存在的最长上升子序列为[3, 4, 6, 7]
。
因此,这段代码高效地解决了最长上升子序列的问题,适合在面对大数据量时使用。