Python 算法题之 俄罗斯套娃信封

本文介绍了Python解决俄罗斯套娃信封问题的动态规划算法,包括错误示例分析,以及两种动态规划方法的实现:一种基于宽度w升序、高度h降序排序,另一种全升序排列并额外判断宽度。这两种方法均能找出能套娃的最长序列,重点在于正确排序和避免相同宽度信封的冲突。
摘要由CSDN通过智能技术生成

Python 算法题之 俄罗斯套娃信封


给出题目🍠

有一个二维整数数组 envelopes ,其中 envelopes 中的每个值代表着一个信封,每个信封的宽度高度以整数对 [ w, h ] 表示,当另一个信封的宽度和高度都比这个信封大的时候,这个信封就可以放进另一个信封里,如同俄罗斯套娃一样
计算 最多有多少个信封能组成一组 “俄罗斯套娃” 信封

比如输入 envelopes = [[5,4],[6,4],[6,7],[2,3]],能组成最多套娃信封组是 [2,3] => [5,4] => [6,7],算法应当输出 3


展示一个错误示例

这是我曾经落入的坑,我决定把它拿出来用于参考,让失败品也有其价值

思路:

  1. 先将二维整数数组 envelopes 依据 w 为第一参考值进行降序排序,如果 w 值重复,则依据 h 为第二参考值进行降序排序
  2. 从索引二 开始循环 envelopes ,预先存储 envelopes 第一个值 new_envs = [envelopes[0]],如果先前值 envelopes[-1]w 与 当前循环值 envelopes[i]w 相同,根据排序后的结果,这个当前循环值就是相同宽度不同长度的信封 且 比上一个信封的 h 小,排除这个信封
  3. 在循环中判断先前值 envelopes[-1]h 是否比当前循环值 envelopes[i] 的小,着意味着大信封套不进小信封里,排除这个信封

结合上面的思路,写出代码

def max_envelopes(envelopes: List[List[int]]) -> int:
    length = len(envelopes)
    envelopes.sort(key=lambda x: (x[0], x[1]), reverse=True)
    new_envs = [envelopes[0]]
    for i in range(1, length):
        # if 当前值的w与上一个值的w不重复 and  当前值的h比上一个值的h小
        if new_envs[-1][0] != envelopes[i][0] and new_envs[-1][1] >= envelopes[i][1]:
            new_envs.append(envelopes[i])

    print(new_envs)             # 查看挑选后的信封
    return len(new_envs)

问题:

  • 如果在 envelopes 排序后,每个信封的 h 值都比后面的值大,确实可以解决问题

  • 可如果排序后出现了某个信封 w 值虽然比后面的值都大,但 h 值比后面的值小,套了这个信封将会导致原本后面可以套进的信封被这个异类 h 值信封拒之门外。要明确的是从这一堆信封中挑选能套娃最多的信封数,而不是依赖排序后的结果从前往后套


动态规划方法

关键点:

  • 根据上面那错误示例的展示,可以得知此问题是需要通过排序解决,envelopes 依据 w 为第一参考值进行升序排序,如果 w 值重复,则依据 h 为第二参考值进行降序排序
  • 排序后认证观察每个信封的 h ,其实就是一个标准的 LIS 问题,小的信封装进大的信封里
  • 需要注意的是因为问题是给信封套娃,那在相同的宽度 w 信封之间,只有高度 h 值最大的信封能选入递增子序列,这也是为什么在排序时 h 需要进行降序排序的原因,确保最终信封序列不会出现相同 w 被选入的情况
    h降序的原因
  • 合法的套娃是大的套小的,不存在小的套大的
def max_envelopes(envelopes: list) -> int:
    if not envelopes:
        return 0
    length = len(envelopes)
    # 排列规则:对宽度 w 进行升序排序,如果 w 相同时,则按高度 h 降序排序
    envelopes.sort(key=lambda x: (x[0], -x[1]))
    dp = [1] * length
    # 小优化,从1开始循环,dp[0]为最小情况1,即只有自己一个的最长递增子序列
    for i in range(1, length):
        for k in range(i):
            if envelopes[i][1] > envelopes[k][1]:
                dp[i] = max(dp[i], dp[k] + 1)
    return max(dp)
  • 时间复杂度: O(n㏒n)
  • 空间复杂度: O(n)

动态规划-全升序 方法

如果就是要 (w, h) 两者都全升序排列,又要能够正确获取答案,这可行吗?答案是可以的

再回去仔细看看上面的 关于 h 降序原因图 ,降序的原因是为了确保相同宽度 w 的信封中只有 h 最大的被选中,其余因为 h 值比先前的小而被 跳过(关键字)

目的是跳过相同宽度 w 的信封,除了降序能跳过外,还可以自己在循环中再加个判断 envelopes[i][0] > envelopes[k][0] ,就多了这段代码,就解决了此问题

def max_envelopes(envelopes: list) -> int:
    if not envelopes:
        return 0
    length = len(envelopes)
    # 排列规则:对宽度 w 进行升序排序,如果 w 相同时,则按高度 h 降序排序
    # 全升序排列
    envelopes.sort(key=lambda x: (x[0], x[1]))			
    dp = [1] * length
    # 小优化,从1开始循环,dp[0]为最小情况1,即只有自己一个的最长递增子序列
    for i in range(1, length):
        for k in range(i):
            if envelopes[i][1] > envelopes[k][1] and envelopes[i][0] > envelopes[k][0]:
                dp[i] = max(dp[i], dp[k] + 1)
    return max(dp)

此方法效率会比 h 降序方法低点,但有助于去理解算法

  • 时间复杂度: O(n㏒n)
  • 空间复杂度: O(n)

总结

  • 这个问题难就难在有没有想到使用排序,正确排序后此问题可以通过 动态规划 LIS 解决
  • 如果在解决这个问题的时候没有能想到排序后再处理,确实会出现无从下手的情况,那么通过这次可以启示我们,再遇到类似问题时,不妨先对数组排序试试

参考资料

  • 题目出之力扣: 俄罗斯套娃信封 leetcode 354
  • 感谢题解里 负雪明烛 大佬 全升序解法的启发
  • 书籍:
    • labuladong大佬 著作 的 《labuladong 的算法小抄》
      这是一本很厉害的书,十分推荐

相关博客😏

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值