俄罗斯套娃信封问题力扣--354

题目

给你一个二维整数数组 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 <= 105
  • envelopes[i].length == 2
  • 1 <= 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
相关推荐
田梓燊4 小时前
2026/4/12 leetcode 1320
算法·leetcode·职场和发展
j_xxx404_4 小时前
力扣题型--链表(两数相加|两两交换链表中的节点|重排链表)
数据结构·c++·算法·leetcode·蓝桥杯·排序算法
AI科技星4 小时前
v=c 物理理论核心参数转换表达式大全
开发语言·线性代数·算法·数学建模·平面
_日拱一卒5 小时前
LeetCode:240搜索二维矩阵Ⅱ
数据结构·线性代数·leetcode·矩阵
WolfGang0073215 小时前
代码随想录算法训练营 Day33 | 动态规划 part06
算法·leetcode·动态规划
aini_lovee5 小时前
半定规划(SDP)求解的 MATLAB 实现
算法
米粒15 小时前
力扣算法刷题 Day 41(买卖股票)
算法·leetcode·职场和发展
幻风_huanfeng5 小时前
人工智能之数学基础:内点法和外点法的区别和缺点
人工智能·算法·机器学习·内点法·外点法
MIngYaaa5205 小时前
The 6th Liaoning Provincial Collegiate Programming Contest - External 复盘
算法