题目
给你一个二维整数数组 envelopes ,其中 envelopes[i] = [wi, hi] ,表示第 i 个信封的宽度和高度。
当另一个信封的宽度和高度都比这个信封大的时候,这个信封就可以放进另一个信封里,如同俄罗斯套娃一样。
请计算 最多能有多少个 信封能组成一组"俄罗斯套娃"信封(即可以把一个信封放到另一个信封里面)。
注意:不允许旋转信封。
示例 1:
输入:envelopes = [[5,4],[6,4],[6,7],[2,3]]
输出:3
解释:最多信封的个数为 3, 组合为: [2,3] => [5,4] => [6,7]。
示例 2:
输入:envelopes = [[1,1],[1,1],[1,1]]
输出:1
提示:
1 <= envelopes.length <= 105envelopes[i].length == 21 <= wi, hi <= 105
思路
这道题其实是求最长递增子序列的进阶版本
先考虑原问题的一个简单版本:所有宽度 w [i]互不相同。
如果按照宽度 w【i】从小到大排序,比如envelopes=[[1,3],[2,1],[3,4],[4,5]]
那么只能把左边的信封装在右边的信封中。这样排序后,就只需关注 h【i】的大小关系。也就是从h=[3,1,4,5]中选一个子序列,满足左边信封的高度小于右边信封的高度,即严格递增子序列。
对于相同的 w【i】,应该怎么处理?
相同的 w【i】 ,至多选一个信封。对于相同的 w 【i】,如果按照 h【i】降序排序,就可以保证至多有一个信封出现在最长递增子序列中了。
比如envelopes=[[1,3],[2,4],[2,3],[2,2],[3,5]]
按照上述规则排序后,得到的h=[3,4,3,2,5]其中的 4,3,2 是降序,至多有一个在最长递增子序列中。这符合题目要求,因为信封 4,3,2 的宽度都是 2,至多能选一个。
对比来看,如果相同的 w【i】,按照 h【i】升序排序,那么envelopes=[[1,3],[2,2],[2,3],[2,4],[3,5]]
得到h=[3,2,3,4,5]
最长递增子序列是 [2,3,4,5],这就搞错了,选了多个相同宽度的信封。
我们平时动态规划的时间复杂度是n^2,在这里给出利用二分查找法把时间复杂度降为O(NlogN)
以求最长递增子序列为题
首先,给你一排扑克牌,我们像遍历数组那样从左到右一张一张处理这些扑克牌,最终要把这些牌分成若干堆。

处理这些扑克牌要遵循以下规则:
只能把点数小的牌压到点数比它大的牌上;如果当前牌点数较大没有可以放置的堆,则新建一个堆,把这张牌放进去;如果当前牌有多个堆可供选择,则选择最左边的那一堆放置。
比如说上述的扑克牌最终会被分成这样 5 堆(我们认为纸牌 A 的牌面是最大的,纸牌 2 的牌面是最小的)。

为什么遇到多个可选择堆的时候要放到最左边的堆上呢?因为这样可以保证牌堆顶的牌有序(2, 4, 7, 8, Q)

按照上述规则执行,可以算出最长递增子序列,牌的堆数就是最长递增子序列的长度。

我们只要把处理扑克牌的过程编程写出来即可。每次处理一张扑克牌不是要找一个合适的牌堆顶来放吗,牌堆顶的牌不是有序吗,这就能用到二分查找了:用二分查找来搜索当前牌应放置的位置。
代码
java语言
java
class Solution {
public int maxEnvelopes(int[][] envelopes) {
int n=envelopes.length;
Arrays.sort(envelopes,(a,b) ->{
if(a[0]==b[0]){
return b[1]-a[1];
}
return a[0]-b[0];
});
int [] height=new int[n];
for(int i=0;i<n;i++){
height[i]=envelopes[i][1];
}
return lengthOfLIS(height);
}
/* 返回 nums 中 LIS 的长度 */
public int lengthOfLIS(int[] nums) {
int piles = 0, n = nums.length;
int[] top = new int[n];
for (int i = 0; i < n; i++) {
// 要处理的扑克牌
int poker = nums[i];
int left = 0, right = piles;
// 二分查找插入位置
while (left < right) {
int mid = (left + right) / 2;
if (top[mid] >= poker)
right = mid;
else
left = mid + 1;
}
if (left == piles) piles++;
// 把这张牌放到牌堆顶
top[left] = poker;
}
// 牌堆数就是 LIS 长度
return piles;
}
}
python语言
python
class Solution:
def maxEnvelopes(self, envelopes: List[List[int]]) -> int:
n = len(envelopes)
# 排序:宽度升序,宽度相同则高度降序
envelopes.sort(key=lambda x: (x[0], -x[1]))
# 提取高度数组
height = [envelopes[i][1] for i in range(n)]
return self.lengthOfLIS(height)
# 二分查找求 LIS,O(n log n)
def lengthOfLIS(self, nums):
piles = 0
n = len(nums)
top = [0] * n
for poker in nums:
# 二分查找左边界
left, right = 0, piles
while left < right:
mid = (left + right) // 2
if top[mid] >= poker:
right = mid
else:
left = mid + 1
if left == piles:
piles += 1
top[left] = poker
return piles