📜  最长递增子序列数(1)

📅  最后修改于: 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)$ 的动态规划解法,但是比较复杂,这里就不再介绍了。