📜  最长递增子序列(N log N)的构造(1)

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

最长递增子序列(N log N)的构造

最长递增子序列是在给定序列中找到一个子序列,使得子序列中元素单调递增,并且长度最长。最长递增子序列问题是计算机科学中的经典问题,具有广泛的应用场景,例如 DNA 序列和股票价格的趋势预测等。这篇文章介绍了使用 N log N 时间复杂度的解决方案,以及相关的实现细节。

解决方案

最长递增子序列问题可以采用动态规划算法来解决,但是动态规划算法的时间复杂度为 O(n^2),不能满足大规模数据的需求。因此,我们需要使用更高效的解决方案。

一种可行的解决方案是通过维护一个单调递增的序列来解决问题。具体地说,我们可以维护一个数组 d,其中 d[i] 表示长度为 i 的递增子序列中的最小末尾元素。我们也可以将 d 数组中的元素看作是当前长度为 i 的所有递增子序列中最小末尾元素的最大值。

我们遍历原序列,对于每个元素,我们可以找到数组 d 中第一个大于等于它的元素,将这个元素替换为该元素。如果数组 d 到达了末尾,我们就将该元素添加到数组 d 的末尾。

最终,数组 d 的长度即为原序列的最长递增子序列的长度。我们可以通过维护一个另外的数组 pre,记录每个 d[i] 对应的上一个元素值,来构造出结果序列。

时间复杂度为 O(n log n),因为对于每个元素,数组 d 的大小最多为 n,而数组的更新操作可以通过二分查找实现。因为二分查找的时间复杂度为 O(log n),所以总时间复杂度为 O(n log n)。

实现细节

我们可以使用二分查找来寻找数组 d 中第一个大于等于当前元素的位置。具体来说,我们可以将二分查找的过程看作是在一个有序数组中插入一个元素的过程。我们首先比较该元素和数组最后一个元素的大小,如果该元素比数组最后一个元素还大,我们就将它直接添加到数组的末尾。否则,我们使用二分查找的方法找到数组 d 中第一个大于等于该元素的位置,并将其替换为该元素。

在实现过程中,我们可以维护一个变量 len,表示当前的数组 d 的长度。我们将数组 d 的长度初始化为1,并将元素 A[0] 添加到数组 d 中。接着,我们遍历原序列 A 的所有元素,对于每个元素 A[i],我们使用二分查找的方法找到数组 d 中第一个大于等于 A[i] 的元素位置,然后用 A[i] 替换该位置。如果数组 d 到达了末尾,我们就将 A[i] 添加到数组 d 的末尾,并将 len 加1。

时间复杂度为 O(n log n),和二分查找的时间复杂度相同。

以下是 Python 代码实现:

def lis(nums):
    if not nums:
        return []
    n = len(nums)
    d = [nums[0]]
    pre = [-1] * n
    len = 1
    for i in range(1, n):
        if nums[i] > d[-1]:
            pre[i] = len - 1
            d.append(nums[i])
            len += 1
        else:
            l, r = 0, len - 1
            loc = -1
            while l <= r:
                mid = (l + r) // 2
                if d[mid] >= nums[i]:
                    loc = mid
                    r = mid - 1
                else:
                    l = mid + 1
            d[loc] = nums[i]
            if loc > 0:
                pre[i] = loc - 1
    ans = []
    x = len - 1
    for i in range(n - 1, -1, -1):
        if pre[i] == x:
            ans.append(nums[i])
            x -= 1
        if x < 0:
            break
    ans.reverse()
    return ans

代码中的 pre 数组用于构造最长递增子序列。我们遍历 pre 数组,如果 pre[i] 等于 j,说明最长递增子序列的第 j 个元素是 nums[i]。我们可以倒序遍历 pre 数组,将这些元素添加到结果序列 ans 中,最后将结果序列反转即可。