俄罗斯套娃信封问题力扣--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
相关推荐
Raink老师10 小时前
【AI面试临阵磨枪-79】实时数据 RAG:订单、商家、物流、天气、动态库存
人工智能·面试·职场和发展
Cosolar10 小时前
Chroma向量库面试学习指南
数据库·人工智能·面试·职场和发展·数据库架构
kkeeper~11 小时前
0基础C语言积跬步之数据在内存中的存储
c语言·数据结构·算法
wabs66613 小时前
关于贪心算法的一些自我总结【力扣45.跳跃游戏II】【灵感来源:代码随想录】
算法·贪心算法·复盘
2401_8769641313 小时前
【湖北专升本】2026湖北专升本真题PDF+备考资料汇总
数据结构·人工智能·经验分享·深度学习·算法·计算机视觉
嗝o゚13 小时前
CANN GE 算子融合——融合算法与调度策略
算法·昇腾·cann·ge
小江的记录本14 小时前
【JVM虚拟机】垃圾回收GC:垃圾回收算法:标记-清除、标记-复制、标记-整理、分代收集(附《思维导图》+《面试高频考点清单》)
java·jvm·后端·python·算法·安全·面试
Ulyanov15 小时前
用声明式语法重新定义Python桌面UI:QML+PySide6现代开发入门(一)
开发语言·python·算法·ui·系统仿真·雷达电子对抗仿真
数据科学小丫15 小时前
特征工程处理
人工智能·算法·机器学习
秦明月1315 小时前
电芯装配测试线安全回路设计实战
经验分享·其他·职场和发展·创业创新·学习方法