LeetCode-354.Russian Doll Envelopes及最长上升子序列问题总结

LeetCode-354.Russian Doll Envelopes及最长上升子序列问题总结

  • 最长上升子序列普通dp法
  • 最长上升子序列解的打印
  • 最长上升子序列NlogN法
  • LeetCode354-Russian Doll Envelopes题解

最长上升子序列普通dp法

生成长度为N的数组dp,dp[i]表示的是在以arr[i]这个数结尾的情况下,arr[0…i]中的最长递增子序列长度

  • 对第一个数arr[0]来说,dp[0] = 1,最长递增子序列就是自己。
  • 当计算到dp[i]的时候,最长递增子序列要以arr[i]结尾,所以我们在arr[0…i-1]中所有比arr[i]小的数可以作为最长递增子序列的倒数第二个数,这些数中,哪个的最长递增子序列更大,就选择哪个。即dp[i] = max(dp[j] + 1) ,0 <= j < i,arr[j] < arr[i]
	 /**
     * 获得最长递增子序列的长度
     * dp[i]表示以arr[0]结尾的情况下,arr[0...i]中的最大递增子序列
     */
    public static int lengthOfLIS(int[] arr){
        if(arr == null || arr.length == 0)return 0;
        int[] dp = new int[arr.length];
        int res = 1;
        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[i],dp[j]+1);
                }
                res = Math.max(res,dp[i]);
            }
        }
        return res;
    }

最长上升子序列解的打印

根据上面的方法可以求得dp数组,我们根据dp数组就可以得到最长上升子序列的解。

  • 从dp数组中的最大值dp[maxi]表示的是以arr[maxi]结尾的,而且是最长的上升子序列;
  • 我们从maxi往前面找,如果前面的某个dp[i],满足arr[i] < arr[maxi] 且dp[maxi] = dp[i] + 1,就说明这个是我们找最长递增子序列时候取的值,可以作为最长递增子序列的倒数第二个数。
  • 然后依次往前找,可以得到解。
    public static int[] getLis(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 - 1; i >= 0; i--){
            if(dp[i] == dp[index] - 1 && arr[i] < arr[index]){
                lis[--len] = arr[i];
                index = i;
            }
        }
        return lis;
    }

最长上升子序列NlogN法

这个方法是使用一个额外的数组ends[],dp[i]记录的还是以arr[i]结尾的最长递增子序列,ends[i]记录的是在所有长度为i+1的递增序列中,最小的结尾数是ends[i]。然后使用二分的方式在ends数组中查找,具体过程如下 :

  • dp[0] = 1,ends[0] = arr[0]表示的是,在所有长度为1的递增序列中,最小结尾是arr[0],此时只有一个数;
  • 用一个right变量记录ends数组的有效范围;
  • 遍历到某个数arr[i]的时候,在ends[]数组中二分查找最左边的>arr[i]的数(二分查找看这篇博客),如果有某个位置l,说明以这个数结尾的最长递增子序列是l + 1,而且此时找到更小的结尾,所以ends[l]要改写成arr[i];如果没有这个位置,说明ends数组中的数都比这个小,所以此时有效区(right)的范围+1,且ends[l] 补到ends数组的后面,dp[i] = l + 1;
  • 一直遍历整个数组,最后的最长长度就是有效区的长度+1(right + 1);
	/**
     * ends数组中 ends[i]表示的是长度为i+1的最长递增子序列中最小结尾是什么
     */
    public static int lengthOfLIS2(int[] arr){
        if(arr == null || arr.length == 0)return 0;
        int[] dp = new int[arr.length];
        int[] ends = new int[arr.length];
        dp[0] = 1;
        ends[0] = arr[0];
        int right = 0;//右边界
        int l = 0,m = 0,r = 0;
        for(int i = 1; i < arr.length; i++){
            l = 0;
            r = right;
            while(l <= r){
                m = l + (r-l)/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 right + 1;
    }

LeetCode354-Russian Doll Envelopes题解

题目链接

题目

给定一些标记了宽度和高度的信封,宽度和高度以整数对形式 (w, h) 出现。当另一个信封的宽度和高度都比这个信封大的时候,这个信封就可以放进另一个信封里,如同俄罗斯套娃一样。请计算最多能有多少个信封能组成一组“俄罗斯套娃”信封(即可以把一个信封放到另一个信封里面)。例如 :
输入: envelopes = [[5,4],[6,4],[6,7],[2,3]]
输出: 3
解释: 最多信封的个数为 3, 组合为: [2,3] => [5,4] => [6,7]。

解析

求解过程:先按a从小到大进行排序,当a相同时,按b从大到小排序。然后求解b的最长递增子序列。
为什么b要按从大到小排列呢?按照最长递增子序列的O(N*logN)方法,当前数arr[i]大于ends数组中所有的数(末尾的最大),我们会将arr[i]添加在ends数组中;否则在ends数组中二分查找第一个大于当前数的数且替换它。所以我们的做法会保证在a相等的情况下,b可以有一个最小值,这样可以摞相对多的数。以达更长的序列,同时也避免了a相同b不相同时摞在一起的情况

import java.util.Arrays;
import java.util.Comparator;

/**
 * 俄罗斯套娃信封问题
 * 题目链接 : https://leetcode-cn.com/problems/russian-doll-envelopes/description/
 */
public class RussianDollEnvelopes {

    private static class Node{
        public int a;
        public int b;

        public Node(int a, int b) {
            this.a = a;
            this.b = b;
        }
    }

    public static class MyComparator implements Comparator<Node>{

        @Override
        public int compare(Node o1, Node o2) {
            if(o1.a == o2.a){
                return o2.b - o1.b;
            }else {
                return o1.a - o2.a;
            }
        }
    }

    /**
     * 最长递增子序列的OLogN方法
     * @param envelopes
     * @return
     */
    public static int maxEnvelopes(int[][] envelopes) {
        if(envelopes == null || envelopes.length == 0 || envelopes[0].length == 0)return 0;
        Node[] nodes = new Node[envelopes.length];
        for(int i = 0; i < envelopes.length; i++){
            nodes[i] = new Node(envelopes[i][0],envelopes[i][1]);
        }
        Arrays.sort(nodes,0,nodes.length,new MyComparator());
        int[] ends = new int[envelopes.length];
        ends[0] = nodes[0].b;
        int right = 0;
        int l = 0,m = 0,r = 0;
        int res = 1;
        for(int i = 1; i < nodes.length; i++){
            l = 0;
            r = right;
            while(l <= r){
                m = l + (r-l)/2;
                if(nodes[i].b > ends[m]){
                    l = m + 1;
                }else {
                    r = m - 1;
                }
            }
            right = Math.max(l,right);
            ends[l] = nodes[i].b;
        }
        return right + 1;
    }

    public static void main(String[] args) {
        int[][] arr = {
                {5,4},{6,4},{6,7},{2,3}
        };
        System.out.println(maxEnvelopes(arr));
    }
}

猜你喜欢

转载自blog.csdn.net/zxzxzx0119/article/details/81224734