Loading

最长递增子序列

最长递增子序列

题目:最长递增子序列

《程序员代码面试指南》第62题 P210 难度:校★★★☆

首先是时间复杂度为O(N2)的方法。

  1. 生成长度为N的数组dp,dp表示在以arr[i]这个数结尾的情况下arr[0..i]中的最大递增子序列长度
  2. 第一个数arr[0]来说,令dp[0]=1,接下来从左到右依次算出以每个位置的数结尾的情况下最长递增子序列长度
  3. 假设计算到位置i,求以arr[i]结尾情况下的最长递增子序列长度,即dp[i]。如果最长递增子序列以arr[i]结尾,那么在arr[0..i-1]中所有比arr[i]小的数都可以作为倒数第二个数。在这么多倒数第二个数的选择中,以哪个数结尾的最大递增子序列更大就选哪个数作为倒数第二个数,所以dp[i]=max{dp[j]+1(0<=j<i,arr[j]<arr[i])}。如果arr[0..i-1]中所有的数都不比arr[i]小,令dp[i]=1即可,说明以arr[i]结尾情况下的最长递增子序列只包含arr[i]。

按照步骤1~步骤3可以计算出dp数组,具体过程请参看如下代码中的getdp1方法。

public int[] getdp1(int[] arr) {
    int[] dp = new int[arr.length];
    for (int i = 0; i < arr.length; i++) {
        dp[i] = 1;
        for (int j = 0; j < i; j++) {
            if (arr[i] > arr[j]) {
                dp[i] = Math.max(dp[i], dp[j] + 1);
            }
        }
    }
    return dp;
}

然后再遍历dp数组找到最大值以及位置从该位置开始向左遍历,依次找到倒数第二、倒数第三个数……

dp数组包含每一步决策的信息,其实根据dp数组找出最长递增子序列的过程就是从某个位置开始逆序还原出决策路径的过程,具体参照如下方法:

public int[] generateLIS(int[] arr, int[] dp) {
    int len = 0;
    int index = 0;
    for (int i = 0; i < dp.length; i++) {
        if (dp[i] > len) {
            len = dp[i];
            index = i;
        }
    }
    int[] lis = new int[len];
    lis[--len] = arr[index];
    for (int i = index; i >= 0; i--) {
        if (arr[i] < arr[index] && dp[i] == dp[index] - 1) {
            lis[--len] = arr[i];
            index = i;
        }
    }
    return lis;
}

整个过程的主方法:

public int[] lis1(int[] arr) {
    if (arr == null || arr.length == 0) {
        return null;
    }
    int[] dp = getdp1(arr);
    return generateLIS(arr, dp);
}

至于时间复杂度为O(NlogN)的方法,主要是对dp数组的生成过程利用二分查找进行优化。生成了一个长度为N数组ends,以及整型变量rightends[0..right]有效区ends[right+1..N-1]无效区。对有效区上的位置b,如果有ends[b]==c,则表示遍历到目前为止在所有长度为b+1的递增序列中最小的结尾数是c。无效区的位置无意义。

具体生成过程可以参照书P212-213,代码如下:

public int[] getdp2(int[] arr) {
    int[] dp = new int[arr.length];
    int[] ends = new int[arr.length];
    ends[0] = arr[0];
    dp[0] = 1;
    int right = 0;
    int l = 0;
    int r = 0;
    int m = 0;
    for (int i = 1; i < arr.length; i++) {
        l = 0;
        r = right;
        while (l <= r) {
            m = (l + r) / 2;
            if (arr[i] > ends[m]) {
                l = m + 1;
            } else {
                r = m - 1;
            }
        }
        right = Math.max(right, l);
        ends[l] = arr[i];
        dp[i] = l + 1;
    }
    return dp;
}

public int[] lis2(int[] arr) {
    if (arr == null || arr.length == 0) {
        return null;
    }
    int[] dp = getdp2(arr);
    return generateLIS(arr, dp);
}
posted @ 2022-03-13 16:40  幻梦翱翔  阅读(77)  评论(0)    收藏  举报