二维动态规划&&二分查找的动态规划&&最长递增子序列&&最长连续递增子序列

题目描述与背景介绍

背景题目:

[674. 最长连续递增序列]https://leetcode-cn.com/problems/longest-continuous-increasing-subsequence/
[300. 最长递增子序列]https://leetcode-cn.com/problems/longest-increasing-subsequence/

这两个都是DP的经典题目,674比较简单。

代码:

class Solution {
    public int findLengthOfLCIS(int[] nums) {
        if(nums.length == 0){//边界判断
            return 0;
        }else if(nums.length == 1){
            return 1;
        }
        int[] dp = new int[nums.length];
        dp[0] = 1;//dp[i]是以nums[i]结尾的最长连续递增子序列的长度
        int maxLen = 1;
        for(int i = 1;i < nums.length;i++){
            if(nums[i] > nums[i - 1]){
                dp[i] = dp[i - 1] + 1;
            }else{
                dp[i] = 1;
            }
            maxLen = Math.max(dp[i],maxLen);
        }
        return maxLen;
    }
}

300题相对来说暴力解法也相对简单。

求解dp[i] 时,要遍历 dp[0] ~ dp[i-1],寻找满足nums[j] < nums[i] 的j(首先j<i),那么dp[i] = dp[j] + 1,若找不到这样的j,则dp[i] = 1,因为每个数字单独都能成为一个子序列。
代码:

class Solution {
    public int lengthOfLIS(int[] nums) {
        if(nums.length == 1){
            return 1;
        }
        int[] dp = new int[nums.length];
        dp[0] = 1;//初始化
        int maxLen = 1;
        for(int i = 1;i < nums.length;i++){
            dp[i] = 1;//每个数字首先单独成为一个序列
            for(int j = 0;j < i;j++){
                if(nums[i] > nums[j]){
                    dp[i] = Math.max(dp[i], dp[j] + 1);//更新dp[i]
                }
            }
            maxLen = Math.max(maxLen, dp[i]);//更新最大长度。
        }
        return maxLen;
    }
}

300题的优化

仔细分析发现,在求解dp[i]时,要遍历nums[0]~nums[i - 1]。总的来说,上述暴力解法的时间复杂度是O(n^2) 。
O(n^2) 的复杂度还是有些太大,而 O(n^2) 下稍微优化有 O(nlogn) 的算法。
寻找满足条件的j时,其实可以利用二分查找。
用f[i]表示递增子序列长度为i的末尾最小元素。比如有2个不同的长度为3的递增子序列,一个末尾元素为9,一个末尾元素为12,那么,此时f[i]=12。
一旦这样规定,f[]就是单调递增的,长度为i(假设i>j)的子序列的末尾最小元素一定大于长度为j的子序列的末尾最小元素。
为什么?
取所有长度为i的子序列构成的集合为A和所有长度为j的子序列构成的集合为B(假设i>j)。
长度为i的子序列集合中一定存在包含某个长度为j的子序列,及存在a∈A,使得b∈B满足b是a的子序列。
f[i]是A中子序列末尾数的最小值,f[j]是B中子序列末尾数的最小值。则f[i]>=a(末尾)>b(末尾)>=f[j],因此f[]严格单调递增。
二分查找时要找到使得f[j]< nums[i] <= f[j+1] 成立的最大的j,返回j。

class Solution {
    public int lengthOfLIS(int[] nums) {
        if(nums.length == 1){
            return 1;
        }
        int[] dp = new int[nums.length];
        dp[0] = 1;//初始化
        int[] f = new int[nums.length + 1];
        f[1] = nums[0];
        int maxLen = 1;
        for(int i = 1;i < nums.length;i++){
            dp[i] = binarySearch(f,1,maxLen,nums[i]) + 1;
            if(f[dp[i]] == 0||nums[i] < f[dp[i]]){//更新f[],如果原来是0,直接更新,如果不是,比较大小。
                f[dp[i]] = nums[i];
            }
            maxLen = Math.max(maxLen, dp[i]);//更新最大长度。
        }
        return maxLen;
    }
    private int binarySearch(int[] f,int first,int end,int curNumber){
        if(end < first){
            return 0;
        }
        if(curNumber <= f[first]){
            return first - 1;
        }
        if(curNumber > f[end]){
            return end;
        }else if(curNumber == f[end]){
            return end - 1;
        }
        if(curNumber > f[(first+end)/2]){
            return binarySearch(f,(first+end)/2 + 1,end,curNumber);
        }else{
            return binarySearch(f,first,(first+end)/2 - 1,curNumber);
        }
    }
}

本题

[354. 俄罗斯套娃信封问题]https://leetcode-cn.com/problems/russian-doll-envelopes/
在本题中,要找到二维的最长递增子序列的长度,同时满足
Wi<Wj<...<Wk
Hi<Hj<...<Hk
但本题并不是一维递增子序列问题的简单扩展,原因是信封不按照数组中的先后顺序。
如何获取套娃时这些信封的先后顺序呢?
简单来说有以下三种方法:

  • 按某一维度升序排列
  • 按某一维度升序排列,出现相等时另一位维度在此基础上降序排列
  • 按面积升序排列
    按某一维度升序排列,出现相等时另一位维度在此基础上降序排列:
    第一维度相等时,无论第二维度如何都不能满足题意,因此,第二维降序可以保证任意子序列只选择第一维度相等的其中一个。
    暴力搜索代码:
class Solution {
    public int maxEnvelopes(int[][] envelopes) {

        Arrays.sort(envelopes,new Comparator<int[]>(){
            public int compare(int[] ob1,int[] ob2){
                if(ob1[0]>ob2[0]){
                    return 1;
                }else if(ob1[0]<ob2[0]){
                    return -1;
                }else{
                    if(ob1[1]<ob2[1]){
                        return 1;
                    }else if(ob1[1]>ob2[1]){
                        return -1;
                    }else{
                        return 0;
                    }
                }
            }
        });

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

二分查找代码:

class Solution {
    public int maxEnvelopes(int[][] envelopes) {

        Arrays.sort(envelopes,new Comparator<int[]>(){
            public int compare(int[] ob1,int[] ob2){
                if(ob1[0]>ob2[0]){
                    return 1;
                }else if(ob1[0]<ob2[0]){
                    return -1;
                }else{
                    if(ob1[1]<ob2[1]){
                        return 1;
                    }else if(ob1[1]>ob2[1]){
                        return -1;
                    }else{
                        return 0;
                    }
                }
            }
        });

        int[] dp = new int[envelopes.length];
        dp[0] = 1;
        int[] f = new int[envelopes.length + 1];
        f[1] = envelopes[0][1];
        if(envelopes.length>1){
            int maxEnvelopes = 1;
            for(int i = 1;i<envelopes.length;i++){
                dp[i] = binarySearch(f,1,maxEnvelopes,envelopes[i][1]) + 1;
                if(f[dp[i]]==0||envelopes[i][1]<f[dp[i]]){//更新f[]数组
                    f[dp[i]] = envelopes[i][1];
                }
                maxEnvelopes = Math.max(maxEnvelopes,dp[i]);
            }
            return maxEnvelopes;
        }else{
            return 1;
        }
    }
    private int binarySearch(int[] f,int first,int end,int curNumber){
        if(curNumber > f[end]){
            return end;
        }else if(curNumber == f[end]){
            return end - 1;
        }
        if(curNumber <= f[first]){
            return first - 1;
        }
        if(curNumber > f[(first + end)/2]){
            return binarySearch(f,(first + end)/2+1,end,curNumber);
        }else{
            return binarySearch(f,first,(first + end)/2-1,curNumber);
        }
    } 
}

排序时只考虑第一维度:

class Solution {
    public int maxEnvelopes(int[][] envelopes) {

        Arrays.sort(envelopes,new Comparator<int[]>(){
            public int compare(int[] ob1,int[] ob2){//排序时只考虑第一维度
                if(ob1[0]>ob2[0]){
                    return 1;
                }else if(ob1[0]<ob2[0]){
                    return -1;
                }else{
                    return 0;
                }
            }
        });

        int[] dp = new int[envelopes.length];
        dp[0] = 1;
        if(envelopes.length>1){
            int maxEnvelopes = 1;
            for(int i = 1;i<envelopes.length;i++){
                dp[i] = 1;
                for(int j = 0;j < i;j++){
                    if(envelopes[i][1] > envelopes[j][1]&&envelopes[i][0] > envelopes[j][0]){//由于第一维度可能存在相等的数,加一个第一维度的比较
                        dp[i] = Math.max(dp[i],dp[j] + 1);
                    }
                }
                maxEnvelopes = Math.max(maxEnvelopes,dp[i]);
            }
            return maxEnvelopes;
        }else{
            return 1;
        }
    }
}
posted @ 2021-03-04 23:48  HickeyZhang  阅读(169)  评论(0)    收藏  举报