LeetCode - Longest Increasing Subsequence和Russian Doll Envelopes

Longest Increasing Subsequence

在一个给定的数组中,找最长的连续增长的序列,序列不必一定要连续。
输入: [10,9,2,5,3,7,101,18]
结果: 4
其实最长的连续序列可能会有多个,比如[2,3,7,18], [2,5,7,101], [2,5.7,18]..

方法一
比较容易想的是传统的DP方法。

  • 定义: dp[i] - 以i为连续增长序列的最后一个位置的最长的长度
  • 初始化: dp[i] = 1。因为每一个位置至少都会连续增长一个数,就是它本身
  • 转换方程: dp[i] = dp[j] + 1 if j < i && array[j] < array[i] && dp[j] >= dp[i]
  • 结果:dp[n - 1], n = input_array.length
class Solution {
    public int lengthOfLIS(int[] nums) {
        if (nums == null || nums.length == 0) {
            return 0;
        }
        
        int m = nums.length;
        int[] dp = new int[m]; //dp[i]: using i as the last position of the input array, dp[i] is the length of the longest increasing subsequence for the subarray
        
        int max = 1;
        for (int i = 0; i < m; i++) {
            dp[i] = 1;
            for (int j = 0; j < i; j++) {
                if (nums[j] < nums[i] && dp[j] >= dp[i]) {
                    dp[i] = dp[j] + 1;
                }
            }
            max = Math.max(max, dp[i]);
        }
        return max;
    }
}

时间复杂度 - O(n ^ 2)
空间复杂度 - O(n)

方法二
上一种方法里,dp[i]的定义是以i为连续增长序列的最后一个位置的最长的长度,在方法二里我们用一个新的定义方法。

  • 定义:tails[i] - i作为连续增长的序列的最后一个位置,tails[i]是对于length = i + 1的连续增长的序列里的值是最小的。
    • 比如我们有输入[1,3,5,2,8,4,6]
    • 对于长度是1的连续增长序列,[1]就是这个长度下,最后一位最小的结果
    • 对于长度是2的连续增长序列,[1,2]就是这个长度下,最后一位最小的结果
    • 对于长度是3的连续增长序列,[1,2,4]就是这个长度下,最后一位最小的结果
    • 对于长度是4的连续增长序列,[1,2,4,6]就是这个长度下,最后一位最小的结果
    • 这个输入里,不能找到长度是5的连续增长的序列,所以结果就是4。基本上,我们只关心某个特定长度下,能当那个长度的subarray的最后一位最小即可。
    • 如果我们将输入数组再增加一位9,我们可以获得一个长度是5的连续增长序列 - [1,2,4,6,9]
    • 而如果我们将输入数组再增加的一位不是9而是3,我们可以得到可以一个跟新的tails[] - [1,2,3,6],即当我们如果想知道知道一个长度为3的连续增长的序列的最后一位的最小值,我们就知道是3
    • 再比如我们将输入数组再增加的一位不是3而是5,我们可以得到[1,2,4,5],即当我们如果想知道一个长度为4的连续增长序列的最后一位的最小值,我们就知道是5了。
  • 初始化:初始连续最长的连续增长序列的长度为0,初始一个tails数组,数组长度等于输入数组长度
  • 转移方程:对于当前的array[i]来说,找到它在tails数组里,在[0, length)范围内的,能跟新tails数组的位置。如果跟新的位置是length的位置,我们就相应的给length+1。
  • 结果:final_length
class Solution {
    public int lengthOfLIS(int[] nums) {
        if (nums == null || nums.length == 0) {
            return 0;
        }
        int m = nums.length, length = 0;
        int[] tails = new int[m]; // tails[i]: for a longest increasing sequence, the length of which is (i + 1), tails[i] is the smallest value among all the candidates 
        
        for (int i = 0; i < m; i++) {
            int position = binarysearch(tails, length, nums[i]);
            tails[position] = nums[i];
            if (position == length) {
                length++;
            }
        }
        return length;
    }
    
    private int binarysearch(int[] tails, int length, int target) {
        if (length == 0) {
            return 0;
        }
        int start = 0, end = length - 1;
        while (start + 1 < end) {
            int mid = start + (end - start) / 2;
            if (tails[mid] == target) {
                return mid;
            } else if (tails[mid] < target) {
                start = mid;
            } else {
                end = mid;
            }
        }
        if (target <= tails[start]) {
            return start;
        }
        if (target <= tails[end]) {
            return end;
        }
        return end + 1;
    }
}

时间复杂度 - O(nlogn)
空间复杂度 - O(n)

Russian Doll Envelopes

输入是一组信封的长度(w, h)。如果一个信封的长和宽都是比另一个封信的长和宽都要大,我们认为这个信封可以套上另一个信封。问在各种组合多,最多的组合能套多少层?
输入: [[5,4],[6,4],[6,7],[2,3]]
结果: 3
解释: [2,3] => [5,4] => [6,7]

这道题比较tricky的地方是如何给这个输入的二维数组(w, h)排序。最容易想到的是我们先按宽度w按递增的方式排序,如果两封信的宽度w一样,那我们就按长度h再递增的排序。按这种方式排
假设我们有输入 - [[1, 3], [1, 4], [1, 5], [2, 3]],且已经按我们定义的排序方式排好序了。
因为宽度已经是排序了,我们能不能单独把长度这一维度拿出来,转化成一道Longest Increasing Subsequence的问题呢?当我们单独把长度这一维度拿出来时,我们得到[3,4,5,3],那最终结果应该是3。但是其实前3个信封并不能依次套入,因为还有宽度相等的情况。

这里如果我们修改一下我们的排序规则,同样还是先按宽度递增排序,如果宽度一样时,我们按长度递减排序。这样我们可以[5,4,3,3],因此结果是1。实际的情况就是我们不能把[1,3]套入[1,4],不能把[1,4]套进[1,5]。
剩下的工作就是基本上和上面的LIS的解法一致。

时间复杂度 - O(n ^ 2)的解法

class Solution {
    public int maxEnvelopes(int[][] envelopes) {
        if (envelopes == null || envelopes.length == 0) {
            return 0;
        }
        
        // sort envelopes by w accendingly.
        // if w is the same, sort envelopes by h decendingly
        Arrays.sort(envelopes, (int[] e1, int[] e2) -> {
            if (e1[0] != e2[0]) {
                return e1[0] - e2[0];
            }  else {
                return e2[1] - e1[1];
            }
        });
        
        int m = envelopes.length, max = 1;
        int[] dp = new int[m];
        for (int i = 0; i < m; i++) {
            dp[i] = 1;
            for (int j = 0; j < i; j++) {
                if (envelopes[i][1] > envelopes[j][1] && dp[j] >= dp[i]) {
                    dp[i] = dp[j] + 1;
                }
            }
            max = Math.max(max, dp[i]);
        }
        return max; 
    }
}

时间复杂度 - O(nlogn)的解法

class Solution {
    public int maxEnvelopes(int[][] envelopes) {
        if (envelopes == null || envelopes.length == 0) {
            return 0;
        }
        
        // sort envelopes by w accendingly.
        // if w is the same, sort envelopes by h decendingly
        Arrays.sort(envelopes, (int[] e1, int[] e2) -> {
            if (e1[0] != e2[0]) {
                return e1[0] - e2[0];
            }  else {
                return e2[1] - e1[1];
            }
        });
        
        int m = envelopes.length, length = 0;
        int[] tails = new int[m];
        
        for (int[] e: envelopes) {
            int position = binarysearch(tails, length, e[1]);
            tails[position] = e[1];
            if (position == length) {
                length++;
            }
        }
        return length;
    }
    
    private int binarysearch(int[] tails, int length, int target) {
        if (length == 0) {
            return 0;
        }
        int start = 0, end = length - 1;
        while (start + 1 < end) {
            int mid = start + (end - start) / 2;
            if (tails[mid] == target) {
                return mid;
            } else if (tails[mid] < target) {
                start = mid;
            } else {
                end = mid;
            }
        }
        if (target <= tails[start]) {
            return start;
        } 
        if (target <= tails[end]) {
            return end;
        }
        return end + 1;
    }
}

你可能感兴趣的:(LeetCode - Longest Increasing Subsequence和Russian Doll Envelopes)