目录

leetcode300. 最长递增子序列

简介:最长递增子序列:给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。动态规划,patience game,LIS,二维LIS

2 动态规划设计

2.1 最长递增子序列

给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。

子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。

示例 1:

1
2
3
输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。

示例 2:

1
2
输入:nums = [0,1,0,3,2,3]
输出:4

示例 3:

1
2
输入:nums = [7,7,7,7,7,7,7]
输出:1

提示:

  • 1 <= nums.length <= 2500
  • -104 <= nums[i] <= 104

解法一

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Solution {
    public int lengthOfLIS(int[] nums) {
        //边界条件: 1 <= nums.length <= 2500
        int n = nums.length;
        // if(n == 1) return 1;
        
        //dp解法:
        //1.定义dp数组的含义,并初始化:dp[i]表示:到i位置为止的最长递增子序列的长度
        int[] dp = new int[n];
        Arrays.fill(dp, 1);
        //2.定义 base case:到第0个位置,只有一个元素,所以最长递增子序列的长度为1,即dp[0] = 1
        // dp[0] = 1;
        //3.找到状态转移方程:dp[i] = max(dp[n] + 1, dp[i]),其中n = i-1, i-2, ..., 0
        for(int i = 0; i < n; i++) {
            for(int j = i - 1; j >= 0; j--) {
                if(nums[i] > nums[j]) {
                    dp[i] = Math.max(dp[i], dp[j] + 1);
                }
            }
        }

        int ans = -1;
        for(int i = 0; i < n; i++) {
            ans = Math.max(ans, dp[i]);
        }
        return ans;
    }
}

解法二

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
class Solution {
    public int lengthOfLIS(int[] nums) {
        //边界条件: 1 <= nums.length <= 2500
        int n = nums.length;
        if(n == 1) return 1;
        
        //patience sort
        int[] top = new int[n]; //牌堆最上方的元素
        int piles = 0; //牌堆总数
        for(int i = 0; i < n; i++) {
            int poker = nums[i];
            int pos = getPos(top, piles, poker);
            if(pos == piles) {
                //将poker放到一个新的牌堆
                top[piles++] = poker; 
            } else {
                //将poker放到第pos个牌堆上方
                top[pos] = poker;
            }
        }

        return piles;

    }

    public int getPos(int[] top, int piles, int poker) {
        //二分搜索插入,把poker放到合适的位置
        int left = 0, right = piles - 1;
        while(left <= right) {
            int mid = (right - left) / 2 + left;
            if(top[mid] < poker) {
                left = mid + 1;
            } else if(top[mid] > poker) {
                right = mid - 1;
            } else if(top[mid] == poker) {
                //收缩右侧边界
                right = mid - 1;
            }
        }
        return left;
    }
}

2.2 二维递增子序列:信封嵌套问题

给你一个二维整数数组 envelopes ,其中 envelopes[i] = [wi, hi] ,表示第 i 个信封的宽度和高度。

当另一个信封的宽度和高度都比这个信封大的时候,这个信封就可以放进另一个信封里,如同俄罗斯套娃一样。

请计算 最多能有多少个 信封能组成一组“俄罗斯套娃”信封(即可以把一个信封放到另一个信封里面)。

注意:不允许旋转信封。

示例 1:

1
2
3
输入:envelopes = [[5,4],[6,4],[6,7],[2,3]]
输出:3
解释:最多信封的个数为 3, 组合为: [2,3] => [5,4] => [6,7]。

示例 2:

1
2
输入:envelopes = [[1,1],[1,1],[1,1]]
输出:1

提示:

  • 1 <= envelopes.length <= 105
  • envelopes[i].length == 2
  • 1 <= wi, hi <= 105

解题思路:先对信封宽度w进行升序排列,如果遇到w相同的情况,则按照高度h降序排列。之后把所有的h作为一个数组,在这个数组上计算出的LIS(最长递增子序列)的长度就是答案。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
    public int maxEnvelopes(int[][] envelopes) {
        int n = envelopes.length;
        Arrays.sort(envelopes, new Comparator<int[]>() {
            // 按宽度升序排列,如果宽度一样,则按高度降序排列
            public int compare(int[] a, int[] b) {
                return a[0] == b[0] ? b[1] - a[1] : a[0] - b[0];
            }
        });
        //对高度数组求解LIS
        int[] height = new int[n];
        for(int i = 0; i < n; i++) {
            height[i] = envelopes[i][1];
        }
        return lengthOfLIS(height);
    }
    public int lengthOfLIS(int[] nums) {
        //LIS的实现参考2.1
    }
}

总结

二分查找

找出第一个大于等于自身元素的位置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public int getPos(int[] top, int piles, int poker) {
    //二分搜索插入,把poker放到合适的位置
    int left = 0, right = piles - 1;
    while(left <= right) {
        int mid = (right - left) / 2 + left;
        if(top[mid] < poker) {
            left = mid + 1;
        } else if(top[mid] > poker) {
            right = mid - 1;
        } else if(top[mid] == poker) {
            //收缩右侧边界
            right = mid - 1;
        }
    }
    return left;
}

数组排序

nums是一个二维数组,[[5,4],[6,4],[6,7],[2,3]]

排序规则:优先第一维度升序,其次第二维度降序

1
2
3
4
5
Arrays.sort(nums, new Comparator<int[]>() {
    public int compare(int[] a, int[] b) {
        return a[0] == b[0] ? b[1] - a[1] : a[0] - b[0];
    }
});