LeetCode 354. 俄罗斯套娃信封问题(Python、动态规划LIS、贪心+二分查找)

学习这一算法的思想,解决二维LIS问题,字节跳动原题,值得反复学习

题目描述

该问题为最长递增子序列的二维问题。

我们要找到最长的序列,且满足 seq[i+1] 中的元素大于 seq[i] 中的元素。

该问题是输入是按任意顺序排列的——我们不能直接套用标准的 LIS 算法,需要先对数据进行排序。我们如何对数据进行排序,以便我们的 LIS 算法总能找到最佳答案?

我们可以在这里找到最长递增子序列的解决方法。如果您不熟悉该算法,请先理解该算法,因为它是解决此问题的前提条件。

算法:

假设我们知道了信封套娃顺序,那么从里向外的顺序必须是按 w 升序排序的子序列。

在对信封按 w 进行排序以后,我们可以找到 h 上最长递增子序列的长度。、

我们考虑输入 [[1,3],[1,4],[1,5],[2,3]],如果我们直接对 h 进行 LIS 算法,我们将会得到 [3,4,5],显然这不是我们想要的答案,因为 w 相同的信封是不能够套娃的。

为了解决这个问题。我们可以按 w 进行升序排序,若 w 相同则按 h 降序排序。(我的理解是:若 h 按降序排,那么就可以过滤掉 w 相同的情况) 则上述输入排序后为 [[1,5],[1,4],[1,3],[2,3]],再对 h 进行 LIS 算法可以得到 [5],长度为 1,是正确的答案。这个例子可能不明显。

我们将输入改为 [[1,5],[1,4],[1,2],[2,3]]。则提取 h 为 [5,4,2,3]。我们对 h 进行 LIS 算法将得到 [2,3],是正确的答案。

注意二分查找中的代码注释

class Solution:
    def maxEnvelopes(self, envelopes: List[List[int]]) -> int:
        self.quickSort(envelopes, 0, len(envelopes) - 1)    # 快排, envelopes.sort(key=lambda x: (x[0], -x[1]))
        # 贪心算法求h的LIS
        d = []
        for i in range(len(envelopes)):
            if not d or envelopes[i][1] > d[-1]:
                d.append(envelopes[i][1])
            else:
                loc = self.bisect_right(d, envelopes[i][1])
                d[loc] = envelopes[i][1]
        return len(d)

    def quickSort(self, nums: '二维列表', left, right):
        if left < right:
            partition_index = self.partition(nums, left, right)
            self.quickSort(nums, left, partition_index - 1)
            self.quickSort(nums, partition_index + 1, right)

    def partition(self, nums, left, right):
        pivot = left
        index = pivot + 1
        for i in range(index, right+1):
            # 按第一个元素升序,第二个元素降序排
            if (nums[i][0] < nums[pivot][0]) or (nums[i][0] == nums[pivot][0] and nums[i][1] > nums[pivot][1]):
                self.swap(nums, i, index)
                index += 1
        self.swap(nums, index-1, pivot)
        return index - 1

    def swap(self, nums, i, j):
        nums[i], nums[j] = nums[j], nums[i]

    def bisect_right(self, arr: '一维数组', k: '要插入的数字'):
        left = 0
        right = len(arr) - 1
        index = right

        while left <= right:    # 注意写二分查找时要有=
            mid = (left + right) // 2
            if k <= arr[mid]:
                right = mid - 1
                index = mid     # =mid而不是mid-1
            else:
                left = mid + 1
        return index

在这里插入图片描述
复杂度分析

  • 时间复杂度: O ( N log ⁡ N ) O(N \log N) O(NlogN)。其中 N N N 是输入数组的长度。排序和 LIS 算法都是 O ( N log ⁡ N ) O(N \log N) O(NlogN)
  • 空间复杂度: O ( N ) O(N) O(N),需要一个数组 d d d,它的大小可达 N N N。另外,我们使用的排序算法也可能需要额外的空间。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值