俄罗斯套娃信封问题力扣--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
相关推荐
YaraMemo1 天前
数学优化问题中的三大转化:多目标转化为单目标,多变量转化为单变量,有约束转化为无约束
人工智能·算法·5g·信息与通信·信号处理
Ailan_Anjuxi1 天前
【附Python源码】使用minGPT训练自己的小型GPT语言模型
算法
QuZero1 天前
StampedLock Mechanism
java·算法
云泽8081 天前
二叉树高阶笔试算法题精讲(二):非递归遍历与序列构造全解析
c++·算法·面试
小O的算法实验室1 天前
2026年ESWA,基于固定机巢的无人机输电杆塔、变电站与配电杆混合巡检任务分配与路径规划,深度解析+性能实测
算法·论文复现·智能算法·智能算法改进
sali-tec1 天前
C# 基于OpenCv的视觉工作流-章60-点点距离
图像处理·人工智能·opencv·算法·计算机视觉
nlpming1 天前
OpenCode Skills 文档
算法
无限进步_1 天前
二叉搜索树完全解析:从概念到实现与应用场景
c语言·开发语言·数据结构·c++·算法·github·visual studio
05候补工程师1 天前
深度解构 ROS 2:如何手动调通 Nav2 A* 路径规划引擎
linux·人工智能·经验分享·算法·机器人
上弦月-编程1 天前
【C语言逻辑题】谋杀案凶手是谁?——经典矛盾推理题详解
算法