📜  最长递增子序列 | DP-3(1)

📅  最后修改于: 2023-12-03 14:55:23.429000             🧑  作者: Mango

最长递增子序列 | DP-3

最长递增子序列(Longest Increasing Subsequence,简称 LIS)是指在一个给定的序列中,找到一个最长的子序列使得其中的元素是按升序排列的。

例如,给定序列 {10, 22, 9, 33, 21, 50, 41, 60},则其 LIS 的长度为 5,具体为 {10, 22, 33, 50, 60}。

解法一:暴力枚举

这是最简单直观的解法,即对于每个元素,枚举它之前的所有元素,找到其中最长的递增子序列,并在其中加入当前元素。时间复杂度为 $O(2^n)$,不适用于数据量大的情况。

解法二:动态规划

动态规划是一种求解最优问题的有效方法。对于 LIS,可以使用动态规划求解。设 $L_i$ 表示以第 $i$ 个元素为结尾的最长递增子序列长度,则有如下状态转移方程:

$$ L_i=\max(L_j+1) \quad \text{if } j < i \text{ and } arr_j < arr_i $$

即以第 $i$ 个元素为结尾的最长递增子序列长度等于前面小于第 $i$ 个元素的所有元素的最长递增子序列长度加一。

时间复杂度为 $O(n^2)$。

def LIS(arr):
    n = len(arr)
    if n == 0:
        return 0
    dp = [1] * n
    for i in range(1, n):
        for j in range(i):
            if arr[j] < arr[i]:
                dp[i] = max(dp[i], dp[j] + 1)
    return max(dp)
解法三:贪心 + 二分查找

在解法二的基础上,考虑优化时间复杂度。注意到状态转移方程中,我们只需要在前面小于当前元素的所有元素中找到 LIS 长度最大的一个就可以了,因此可以使用二分查找来优化。

具体来说,我们维护一个递增的数组 $d$,其中每个元素代表以当前长度结尾的 LIS 中末尾元素的最小值。对于每个元素,我们使用二分查找找到 $d$ 数组中第一个大于等于它的位置,并将该位置上的元素替换为当前元素。

时间复杂度为 $O(n \log n)$。

def LIS(arr):
    n = len(arr)
    if n == 0:
        return 0
    d = [arr[0]]
    for i in range(1, n):
        if arr[i] > d[-1]:
            d.append(arr[i])
        else:
            l, r = 0, len(d) - 1
            while l < r:
                mid = (l + r) // 2
                if d[mid] >= arr[i]:
                    r = mid
                else:
                    l = mid + 1
            d[l] = arr[i]
    return len(d)
总结

LIS 是一个经典的算法问题,其动态规划解法和贪心 + 二分查找解法都有其标志性的思想。在实际应用中,根据数据量和时间复杂度的需求,可以选择相应的解法。