专栏首页算法码上来每日算法系列【LeetCode 354】俄罗斯套娃信封问题

每日算法系列【LeetCode 354】俄罗斯套娃信封问题

题目描述

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

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

说明:不允许旋转信封。

示例1

输入:
envelopes = [[5,4],[6,4],[6,7],[2,3]]
输出:
3
解释:
最多信封的个数为 3, 组合为: [2,3] => [5,4] => [6,7]。

题解

题目要求矩形一个套着一个,然后求出最多套多少个,而一个矩形能套在另一个矩形上面的条件是长宽都大于另一个。

那么我们可以按照长度从小到大排个序,这样只有排在后面的矩形可以套在前面的矩形上。但是宽度也有限制条件,也得大于前面的矩形,那么问题就转化成了,把宽度看成一个序列,找到一个最长的上升序列,序列的长度就是我们要的答案。但是这里有个问题,就是矩形如果是相同长度,它们的宽度按照什么来排序呢?如果也是从小到大排,那么可能会出现多个相同长度的矩形套在一起,这是不符合题意的。所以我们对相同长度的矩形采取宽度降序的方法排序,这样它们之中最多只会被选中一个了。

那么问题就变成了经典的最长上升子序列问题了

方法1

用 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 数组中最大的值。

这种方法时间复杂度是 ,如果序列太长会超时。本题中 c++ 没有超时,但是 python 超时了。

方法2

那么有什么方法来优化呢?下面介绍一种二分优化方法。

这次假设 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 就是答案。

但是这样看起来复杂度没有变啊,其实这里有一个很好的性质,因为长度越大的序列,最后一个元素的最小值一定是大于长度小的序列最后一个元素的,所以 dp 数组是单调递增的,这样我们就可以用二分来寻找 a[i] 适合插入的位置。时间复杂度降到了 。

代码

方法1(c++)

class Solution {
public:
    int maxEnvelopes(vector<vector<int>>& envelopes) {
        int n = envelopes.size();
        if (n < 1) return 0;
        sort(envelopes.begin(), envelopes.end(), cmp);
        int res = 1;
        vector<int> dp(n, 1);
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < i; ++j) {
                if (envelopes[j][1] < envelopes[i][1]) {
                    dp[i] = max(dp[i], dp[j] + 1);
                }
            }
            res = max(res, dp[i]);
        }
        return res;
    }

    static bool cmp(const vector<int>& a, const vector<int>& b) {
        if (a[0] != b[0]) return a[0] < b[0];
        return a[1] > b[1];
    }
};

方法1(python)

class Solution:
    def maxEnvelopes(self, arr: List[List[int]]) -> int:
        n = len(arr)
        arr.sort(key=lambda x: (x[0], -x[1]))
        nums = [i[1] for i in arr]
        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

方法2(c++)

class Solution {
public:
    int maxEnvelopes(vector<vector<int>>& envelopes) {
        int n = envelopes.size();
        if (n < 1) return 0;
        sort(envelopes.begin(), envelopes.end(), cmp);
        vector<int> dp(n+1, INT_MAX);
        int len = 0;
        for (int i = 0; i < n; ++i) {
            int idx = lower_bound(dp.begin(), dp.end(), envelopes[i][1]) - dp.begin();
            dp[idx] = envelopes[i][1];
            len = max(len, idx);
        }
        return len+1;
    }

    static bool cmp(const vector<int>& a, const vector<int>& b) {
        if (a[0] != b[0]) return a[0] < b[0];
        return a[1] > b[1];
    }
};

方法2(python)

from bisect import bisect_left

class Solution:
    def maxEnvelopes(self, arr: List[List[int]]) -> int:
        arr.sort(key=lambda x: (x[0], -x[1]))
        nums = [i[1] for i in arr]
        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)

后记

这题还有智障解法:就是两两遍历每一个矩形对,根据包含关系建立一个拓扑图,然后求图上的最长距离。没错,我刚开始就是这么想的,我是智障。这个方法没有错,还真能过这题,就是时间太慢了。

作者简介:godweiyang知乎同名华东师范大学计算机系硕士在读,方向自然语言处理与深度学习。喜欢与人分享技术与知识,期待与你的进一步交流~

本文分享自微信公众号 - 算法码上来(GodNLP),作者:godweiyang

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-01-17

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 每日算法系列【LeetCode 943】最短超级串

    这题意思就是,给你 n 个字符串,任意两个字符串如果拼接在一起的话,首尾可能会有重合的部分,那么就按照最长的重合部分拼接上去。要求的是 n 个字符串怎么排列,然...

    godweiyang
  • 【每日算法Day 92】经典面试题:编辑距离

    给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数 。

    godweiyang
  • 每日算法系列【LeetCode 1039】多边形三角剖分的最低得分

    给定 N,想象一个凸 N 边多边形,其顶点按顺时针顺序依次标记为 A[0], A[i], ..., A[N-1]。

    godweiyang
  • LeetCode 276. 栅栏涂色(DP)

    有 k 种颜色的涂料和一个包含 n 个栅栏柱的栅栏,每个栅栏柱可以用其中一种颜色进行上色。

    Michael阿明
  • C++版 - Leetcode 5. Longest Palindromic Substring 解题报告

    提交网址: https://leetcode.com/problems/longest-palindromic-substring/

    Enjoy233
  • Leetcode 516. 最长回文子序列

    输入:"bbbab" 输出:4 解释: 一个可能的最长回文子序列为 "bbbb"。

    zhipingChen
  • Golang Leetcode 956. Tallest Billboard.go

    dp思路 dp方程的键为两个柱子之间的高度差,值为当前高度差情况下,两个柱子的最小高度 状态转移的时候有三种情况,其中后两种可以合并 最后dp[0]保存的...

    anakinsun
  • HDU 1024 Max Sum Plus Plus

    http://acm.hdu.edu.cn/showproblem.php?pid=1024 题意:可不连续的m个子段的最大和 分析:首先由于n很大,所以需要运...

    用户1624346
  • LeetCode 279. Perfect Squares

    ShenduCC
  • Golang Leetcode 343. Integer Break.go

    版权声明:原创勿转 https://blog.csdn.net/anakinsun/article/details/89088860

    anakinsun

扫码关注云+社区

领取腾讯云代金券