📜  最长递增子序列大小(N log N)(1)

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

最长递增子序列大小(N log N)

最长递增子序列(Longest Increasing Subsequence,LIS)问题是指在一个无序的序列中,找到一个子序列,使得这个子序列要求递增,且在所有的递增子序列中,它是最长的。

在解决这个问题时,我们需要在保证子序列递增的前提下,尽可能使得子序列的长度更长,这就需要使用一些巧妙的算法和数据结构。

算法思路

一般情况下,我们使用动态规划算法来解决最长递增子序列问题。具体地,我们设$dp[i]$表示以第$i$个数为结尾的最长递增子序列的长度。则对于所有的$0\leq j<i$,状态转移方程为:

$$dp[i]=\max{dp[j]+1|j<i,a[j]<a[i]}$$

即从前面的所有元素中,选择一个比第$i$个数小的元素$j$,然后加上这个元素的最长递增子序列长度$dp[j]$,得到以第$i$个数为结尾的最长递增子序列的长度。

这个算法需要$O(N^2)$的复杂度来处理整个序列,显然不够高效。而现在有一种复杂度为$O(N\log N)$的算法,可以更快地解决这个问题。

具体思路就是设$L_i$表示长度为$i$的递增子序列的末尾元素的最小值。则我们从前往后遍历整个序列,对于每个元素$a_i$,有以下几种情况:

  1. 若$a_i$比所有长度为$i$的递增子序列的末尾元素都要大,则新增一个长度为$i$的递增子序列,此时末尾元素为$a_i$。

  2. 若存在一个长度为$j(j<i)$的递增子序列的末尾元素$L_j$,满足$L_j<a_i\leq L_{j+1}$,则将$L_{j+1}$更新为$a_i$。

由于所有长度为$i$的递增子序列的末尾元素都是有序的,因此这个更新操作可以二分查找优化到$O(\log N)$的复杂度。同时,由于我们只需要维护长度为$i$的递增子序列的末尾元素的最小值$L_i$,因此空间复杂度也可以优化到$O(N)$。

综上所述,利用这种算法可以在$O(N\log N)$的时间复杂度和$O(N)$的空间复杂度内解决最长递增子序列问题。

代码实现

以Java语言为例,以下是使用上述算法解决最长递增子序列问题的代码实现:

public int lengthOfLIS(int[] nums) {
    int n = nums.length;
    int[] dp = new int[n];
    int len = 0;
    for (int i = 0; i < n; i++) {
        int j = Arrays.binarySearch(dp, 0, len, nums[i]);
        if (j < 0) {
            j = -j - 1;
        }
        dp[j] = nums[i];
        if (j == len) {
            len++;
        }
    }
    return len;
}

其中,Arrays.binarySearch(dp, 0, len, nums[i])表示在长度为len的数组dp的前缀中二分查找第一个大于等于nums[i]的元素。若存在这样的元素,则返回其位置;否则,返回负数$-j-1$,其中$j$为第一个大于nums[i]的元素的位置。由于这个函数只能找到大于等于指定元素的最小位置,而本算法需要找到小于等于指定元素的最大位置,因此需要在返回结果前再进行一次判断。