题目连接
https://leetcode-cn.com/problems/russian-doll-envelopes/
解题思路
前言
根据题目的要求,如果我们选择了 kk 个信封,它们的的宽度依次为 w0 ,w1 ,⋯,wk−1 ,高度依次为 h0 ,h1 ,⋯,hk−1 ,那么需要满足:
w0 < w1 < ⋯ < wk−1
h0 < h1 < ⋯ < hk−1
同时控制 w 和 h 两个维度并不是那么容易,因此我们考虑固定一个维度,再在另一个维度上进行选择。例如,我们固定 w 维度,那么我们将数组envelopes 中的所有信封按照 w 升序排序。这样一来,我们只要按照信封在数组中的出现顺序依次进行选取,就一定保证满足:w0 < w1 < ⋯ < wk−1 了。然而小于等于 ≤ 和小于 < 还是有区别的,但我们不妨首先考虑一个简化版本的问题:
如果我们保证所有信封的 w 值互不相同,那么我们可以设计出一种得到答案的方法吗?
在 w 值互不相同的前提下,小于等于 ≤ 和小于 < 是等价的,那么我们在排序后,就可以完全忽略 w 维度,只需要考虑 h 维度了。此时,我们需要解决的问题即为:
给定一个序列,我们需要找到一个最长的子序列,使得这个子序列中的元素严格单调递增,即上面要求的:
h0 < h1 < ⋯ < hk−1
那么这个问题就是经典的「最长严格递增子序列」问题了,读者可以参考 LeetCode300题解 。最长严格递增子序列的详细解决方法属于解决本题的前置知识点,不是本文分析的重点,因此这里不再赘述。
当我们解决了简化版本的问题之后,我们来想一想使用上面的方法解决原问题,会产生什么错误。当 w 值相同时,如果我们不规定 h 值的排序顺序,那么可能会有如下的情况:
排完序的结果为 [(w, h)] = [(1, 1), (1, 2), (1, 3), (1, 4)],由于这些信封的 w 值都相同,不存在一个信封可以装下另一个信封,那么我们只能在其中选择 1 个信封。然而如果我们完全忽略 w 维度,剩下的 h 维度为 [1, 2, 3, 4],这是一个严格递增的序列,那么我们就可以选择所有的 4 个信封了,这就产生了错误。
因此,我们必须要保证对于每一种 w 值,我们最多只能选择 1 个信封。
我们可以将 h 值作为排序的第二关键字进行降序排序,这样一来,对于每一种 w 值,其对应的信封在排序后的数组中是按照 h 值递减的顺序出现的,那么这些 h 值不可能组成长度超过 1 的严格递增的序列,这就从根本上杜绝了错误的出现。
因此我们就可以得到解决本题需要的方法:
- 首先我们将所有的信封按照 w 值第一关键字升序、h 值第二关键字降序进行排序;
- 随后我们就可以忽略 w 维度,求出 h 维度的最长严格递增子序列,其长度即为答案。
class Solution {
public:
int maxEnvelopes(vector<vector<int>>& envelopes) {
if(envelopes.empty()) return 0;
// 排序,C++11的方式重载sort(暂时不会)
sort(envelopes.begin(), envelopes.end(), [](const auto& e1, const auto& e2) {
return e1[0] < e2[0] || (e1[0] == e2[0] && e1[1] > e2[1]);
});
// 直接将最长严格递增子序列的代码copy上
const int N = 2505;
int len = 1, d[N];
d[len] = envelopes[0][1]; // 初始化d[1]为nums[0],因为若数组长度不为0,则len至少为1
for(int i = 1;i < envelopes.size(); i++) {
if(d[len] < envelopes[i][1]) d[++len] = envelopes[i][1];
else {
int l = 1, r = len, m, pos = 0; // pos是为了保证当全部数都比nums[i]大时,将d[pos+1]赋值为nums[i]
while(l <= r) {
m = l + r >> 1;
if(d[m] < envelopes[i][1]) {
pos = m;
l = m + 1;
}
else {
r = m - 1;
}
}
d[pos + 1] = envelopes[i][1];
}
}
return len;
}
};
总结
可以获取的技巧为:
通过将第二关键字降序排列,再通过“最长严格递增子序列”算法求长度的时候,可以避免第一关键字不严格单调的情况。从而求出两个关键字都严格单调的最长长度。