DP、二分优化-LeetCode354. 俄罗斯套娃信封问题(隐晦的LIS)(Python)

1、题目描述

给定一些标记了宽度和高度的信封,宽度和高度以整数对形式 (w, h) 出现。当另一个信封的宽度和高度都比这个信封大的时候,这个信封就可以放进另一个信封里,如同俄罗斯套娃一样。

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

说明: 不允许旋转信封。

2、代码详解

相似题

DP、二分-LeetCode300. 最长上升子序列LIS(Python)

一个矩形能套在另一个矩形上面的条件是长宽都大于另一个。

可以按照长度从小到大排个序,这样只有排在后面的矩形可以套在前面的矩形上。但是宽度也有限制条件,也得大于前面的矩形。那么问题就转化成了,把宽度看成一个序列,找到一个最长的上升序列,序列的长度就是我们要的答案。

但是这里有个问题,就是矩形如果是相同长度,它们的宽度按照什么来排序呢?如果也是从小到大排,那么可能会出现多个相同长度的矩形套在一起,这是不符合题意的

所以我们对相同长度的矩形采取宽度降序的方法排序,这样它们之中最多只会被选中一个了。那么问题就变成了经典的最长上升子序列问题了。

法一:O(N^2),超时

用 dp[i] 表示以第 i 个元素 a[i] 结尾的最长子序列的长度,那么我们可以遍历所有 a[i] 之前的元素 a[j] ,如果 a[j] 小于 a[i] ,那就说明 a[i] 可以加在 a[j] 后面,然后长度就变成了 dp[j] + 1 。所以遍历所有符合条件的 j ,找到长度最长的,然后更新 dp[i] = dp[j] + 1 。最后的答案就是 dp 数组中最大的值。

时间复杂度是 O(N^2),如果序列太长会超时。本题中 c++ 没有超时,但是 python 超时了。

# 法一:dp,O(n*n)超时
class Solution(object):
    def maxEnvelopes(self, envelopes):
        """
        :type envelopes: List[List[int]]
        :rtype: int
        """
        # case: [[5, 4], [6, 4], [6, 7], [2, 3]]

        n = len(envelopes)
        envelopes.sort(key=lambda x: (x[0], -x[1]))  # 宽度w升序排;当w相等时,高度h降序排, [[2, 3], [5, 4], [6, 7], [6, 4]]

        # 转换成求LIS
        nums = [i[1] for i in envelopes]  # nums是高度序列, [3, 4, 7, 4]
        dp = [1] * n
        res = 0
        for i in range(n):
            for j in range(i):
                if nums[j] < nums[i]:
                    dp[i] = max(dp[i], dp[j] + 1)
            res = max(res, dp[i])
        return res

envelopes = [[5,4],[6,4],[6,7],[2,3]]
s = Solution()
print(s.maxEnvelopes(envelopes))

法二:二分优化方法

这次假设 dp[len] 表示长度为 len 的上升子序列最后一个元素的最小值。这个值要尽量小,什么意思呢?也就是相同长度的上升序列,最后一个元素小的那个序列,后面可以加的元素可选择余地肯定更大。那么这个数组怎么更新呢?

初始的时候 len 就是 0 ,因为没有找到任何上升子序列。如果现在找到了长度为 len 的子序列,然后最后一个元素最小值是 dp[len] ,这时候来了一个新元素 a[i] ,如果它比 dp[len] 大,说明 a[i] 可以加在 dp[len] 后面,那么 len 就变成了 len + 1 , 并且 dp[len + 1] 更新为 a[i]。

那如果 a[i] 小于等于 dp[len] 呢?那就继续往前遍历,找到第一个 dp[l] < a[i] <= dp[l+1] 的位置,这个位置说明了什么呢?说明了 a[i] 可以加在 dp[l] 后面构成长度为 l + 1 的子序列,并且 dp[l+1] 可以变得更小,所以更新为 a[i] 。这样 a[i] 就处理完了,最后 len 就是答案。

from bisect import bisect_left
class Solution(object):
    def maxEnvelopes(self, envelopes):
        """
        :type envelopes: List[List[int]]
        :rtype: int
        """
        envelopes.sort(key=lambda x: (x[0], -x[1]))
        nums = [i[1] for i in envelopes]
        dp = []
        for i in range(len(nums)):
            idx = bisect_left(dp, nums[i])
            if idx == len(dp):
                dp.append(nums[i])
            else:
                dp[idx] = nums[i]
        return len(dp)

envelopes = [[5,4],[6,4],[6,7],[2,3]]
s = Solution()
print(s.maxEnvelopes(envelopes))

https://zhuanlan.zhihu.com/p/103144873

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值