📅  最后修改于: 2023-12-03 14:55:23.475000             🧑  作者: Mango
最长递增子序列(Longest Increasing Subsequence,简称 LIS)指的是在给定的一个序列中,找到一个其中的最长的严格递增子序列,这种子序列不一定是连续的。
举个例子,序列 [4,2,4,5,3,7] 的最长递增子序列是 [2,4,5,7]。
最简单的解法是暴力枚举所有子序列,然后找出其中的最长递增子序列。但是明显这个算法的时间复杂度是 $O(2^n)$,显然无法接受。
所以我们需要一种更加高效的算法来解决这个问题。
显然我们可以使用动态规划来解决这个问题,设 $dp[i]$ 表示以第 $i$ 个数结尾的最长递增子序列长度,则有:
$$ dp[i]=\max_{j<i,a_j<a_i}{dp[j]}+1 $$
其中 $j<i,a_j<a_i$ 表示要找到所有比 $a_i$ 小的 $a_j$。
最终的答案就是所有 $dp[i]$ 中的最大值。这个算法的时间复杂度为 $O(n^2)$。
下面是一个实现,假设输入的序列为 $a$。
n = len(a)
dp = [1] * n
for i in range(1, n):
for j in range(i):
if a[j] < a[i]:
dp[i] = max(dp[i], dp[j] + 1)
ans = max(dp)
还有一种比较巧妙的解法是使用贪心加二分查找。我们维护一个数组 $d$,其中 $d[i]$ 表示长度为 $i$ 的递增子序列中结尾最小的那个数。
接下来我们遍历输入序列,如果某个数加入到 $d$ 中之后,得到的新数组 $d'$ 的长度 $\geq$ 原先的数组长度,则将原先数组拓展成 $d'$。
当然,如果加入这个数之后,新的 $d'$ 长度并没有达到原先的长度,那么我们可以使用二分查找找到第一个大于等于这个数的位置,然后将这个位置上的数替换为这个数。这样做的原因是可以让 $d$ 中结尾较小的那些子序列更有可能继续增长。
最终,$d$ 的长度就是最长递增子序列的长度。这个算法的时间复杂度为 $O(n \log n)$。
下面是一个实现,假设输入的序列为 $a$。
from bisect import bisect_left
n = len(a)
d = [a[0]]
for x in a[1:]:
if x > d[-1]:
d.append(x)
else:
i = bisect_left(d, x)
d[i] = x
ans = len(d)
最长递增子序列是一个经典的问题,有多种解法,其中动态规划和贪心+二分查找的时间复杂度都比较优秀。
值得注意的是,这里介绍的动态规划解法不是最优的,对于这个问题还有一种 $O(n \log n)$ 的动态规划解法,但是比较复杂,这里就不再介绍了。