Loading

最长递增子序列

最长递增子序列

题目:给定数组 arr,返回 arr 的最长递增子序列。

举例:arr=[2,1,5,3,6,4,8,9,7],返回的最长递增子序列为[1,3,4,8,9]。

方法一:动态规划

这题递归可不好搞吧,时间复杂度貌似为o(n^3)。

我们用动态规划,leetcode仅仅让求最长递归子序列的长度,那么dp数组的dp[i]就是以arr[i]为结尾的最长递归子序列长度。(为啥dp[i] 的意义不是以arr[i]为开头的最长长度呢?因为我们连dp[0]都没法初始化,所以不行)

dp[0] 很简单我们就知道是1

dp[1] 为如果arr[1] > arr[0] 则dp[1] = dp[0] + 1

dp[2] 如果arr[2] > arr[0] ,我们还不能着急赋值,如果arr[2] 也大于 arr[1] ,那么我们应该选择dp[0] 和 dp[1] 中最大的一个:dp[2] = Math.max(dp[0], dp[1]) + 1

dp[3] 在arr[3] > arr[i] (0<= i < 3) 的情况下,找到dp[i] 中最大的,加到dp[3]

代码:

    /**
     * 根据arr,获得dp数组,dp数组意义为以第i个元素结尾的最长递增子序列
     * @param arr
     * @return
     */
    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[j] < arr[i]) {
                    dp[i] = Math.max(dp[j] + 1, dp[i]);
                }
            }
        }
        return dp;
    }

得到dp数组后,我们就可以还原最长递增子序列了

在dp数组中找到最大值len,这个最大值就是最长递增子序列的长度。

这个最大值在dp数组中的索引值index,然后最长递增子序列的最后一个元素就是arr[index]

然后我们开始找最长递增子序列的倒数第二个元素,它满足:倒数第二个元素肯定比最后一个元素值小,而且倒数第二个元素的dp[i] 正好等于倒数第一元素的dp[i] - 1

然后就是倒数第三个元素和倒数第二个元素比较。。。。。。

代码:

    /**
     * 根据dp数组的信息:结尾的索引,最长递增子序列的长度。从而求出最长递增子序列
     * @param arr
     * @param dp
     * @return
     */
    public int[] generateLIS(int[] arr, int[] dp) {
        int len = 0;
        int index = 0;
        // 找出dp数组的最大值,和最大值的index
        for (int i = 0; i < arr.length; i++) {
            if (len < dp[i]) {
                len = dp[i];
                index = i;
            }
        }
        int[] res = new int[len];
        res[--len] = arr[index];
        for (int i = index - 1; i >= 0; i--) {
            if (arr[i] < arr[index] && dp[i] == dp[index] - 1) {
                res[--len] = arr[i];
                index = i;
            }
        }
        return res;
    }

主方法:

    // 主方法
    public int[] lengthOfLIS(int[] arr) {
        if (arr.length == 0 || arr == null) {
            return null;
        }
        int[] dp = getdp1(arr);
        return generateLIS(arr, dp);
    }

方法二:动态规划 + 二分

我感觉左神和leetcode上的题解思路都是一致的,只不过左神用ends,leetcode上用tail,不过leetcode上给了更详细的思考过程。

添加了ends,right。ends数组的意义:ends[0] = 2,目前为止长度为1的递增子序列中的结尾值最小为2

ends[0..1]=[1,5]表示目前为止,长度为1的递增子序列中的结尾值最小为1,长度为2的递增子序列中的结尾值最小为5

ends[0..2]=[1,3,6]表示目前为止,长度为1的递增子序列中的结尾值最小为1,长度为2的递增子序列中的结尾值最小为3,长度为3的递增子序列中的结尾值最小为6

代码:

    /**
     * 在查找ends数组中使用二分法,因此降低了时间复杂度
     * @param arr
     * @return
     */
    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;
    }

信封嵌套

给定一个 N 行 2 列的二维数组,每一个小数组的两个值分别代表一个信封的长和宽。如果信封 A 的长和宽都小于信封 B,那么信封 A 可以放在信封 B 里,请返回信封最多嵌套多少层。

matrix = {{3,4},
{2,3},
{4,5},
{1,3},
{2,2},
{3,6},
{1,2},
{3,2},
{2,4}
}

信封最多可以套 4 层,从里到外分别是{1,2},{2,3},{3,4},{4,5},所以返回 4。

这题,既需要长度递增,也需要宽度递增。

先对长度递增排序,对于长度相等的对其宽度递减排序。

按此规律对信封排序,排序后的信封宽度从左到右为{3,2,4,3,2,6,4,2,5}

在这个数组中找到最长递增子序列的长度,就是所求。

    public class Envelope{
        private int len;
        private int wid;

        public Envelope(int len, int wid) {
            this.len = len;
            this.wid = wid;
        }
    }
   
    public class EnvelopeComparator implements Comparator<Envelope> {
        @Override
        public int compare(Envelope o1, Envelope o2) {
            return o1.len != o2.len ? o1.len - o2.len : o2.wid - o1.wid;
        }
    }
    public Envelope[] getSortedEnvelopes(int[][] matrix) {
        Envelope[] res = new Envelope[matrix.length];
        for (int i = 0; i < matrix.length; i++) {
            res[i] = new Envelope(matrix[i][0], matrix[i][1]);
        }
        Arrays.sort(res, new EnvelopeComparator());
        return res;
    }
    
    public int maxEnvelopes(int[][] matrix) {
        Envelope[] envelopes = getSortedEnvelopes(matrix);
        int[] ends = new int[matrix.length];
        ends[0] = envelopes[0].wid;
        int right = 0;
        int l = 0;
        int r = 0;
        int m = 0;
        for (int i = 1; i < envelopes.length; i++) {
            l = 0;
            r = right;
            while (l <= r) {
                m = (l + r) / 2;
                if (envelopes[i].wid > ends[m]) {
                    l = m + 1;
                } else {
                    r = m - 1;
                }
            }
            right = Math.max(right, l);
            ends[l] = envelopes[i].wid;
        }
        return right + 1;
    }
posted @ 2021-06-04 13:00  KeBoom  阅读(333)  评论(0编辑  收藏  举报