⭐算法OJ⭐俄罗斯套娃信封问题【排序 + LIS】Russian Doll Envelopes
问题描述
LeetCode 354. 俄罗斯套娃信封问题(Russian Doll Envelopes)
给定一个二维整数数组 envelopes
,其中 envelopes[i] = [w_i, h_i]
表示第 i
个信封的宽度和高度。当一个信封的宽度和高度都比另一个信封大时,它可以套进去(类似俄罗斯套娃)。求最多能有多少个信封组成“套娃”序列?
示例:
输入:envelopes = [[5,4],[6,4],[6,7],[2,3]]
输出:3
解释:最多可以套成 [2,3] → [5,4] → [6,7] 这样的序列。
解题思路
这个问题可以转化为 二维的最长递增子序列(LIS)问题。我们需要找到一个信封序列,使得每个信封的宽度和高度都严格大于前一个信封的宽度和高度。
关键步骤
-
排序: 首先对信封进行排序,按照宽度
w
升序排列。如果宽度相同,则按照高度h
降序排列。- 这样做的目的是确保在宽度相同时,高度较大的信封排在前面,从而避免同一宽度的信封被错误地计入序列(因为宽度必须严格递增)。
-
最长递增子序列(LIS): 在排序后的高度数组上,使用 贪心 + 二分查找 的方法求解最长递增子序列的长度。
- 因为宽度已经满足非递减,只需保证高度严格递增即可。
C++ 代码实现
#include <vector>
#include <algorithm>
using namespace std;
class Solution {
public:
int maxEnvelopes(vector<vector<int>>& envelopes) {
if (envelopes.empty()) return 0;
// 排序:按宽度升序,宽度相同则按高度降序
sort(envelopes.begin(), envelopes.end(), [](const vector<int>& a, const vector<int>& b) {
return a[0] < b[0] || (a[0] == b[0] && a[1] > b[1]);
});
// 对高度数组求 LIS
vector<int> dp;
for (const auto& env : envelopes) {
int h = env[1];
auto it = lower_bound(dp.begin(), dp.end(), h);
if (it == dp.end()) {
dp.push_back(h);
} else {
*it = h;
}
}
return dp.size();
}
};
代码解析
-
排序部分:
sort(envelopes.begin(), envelopes.end(), [](...) { ... })
- 使用自定义比较函数,先按宽度
w
升序,宽度相同则按高度h
降序。 - 例如:
[[5,4],[6,4],[6,7],[2,3]]
排序后为[[2,3],[5,4],[6,7],[6,4]]
。
-
LIS 部分:
dp
数组维护当前的最长递增子序列。- 遍历排序后的高度
h
,用lower_bound
找到第一个不小于h
的位置:- 如果
h
比dp
中所有元素都大,则直接加入dp
。 - 否则,用
h
替换dp
中第一个不小于它的元素(保证dp
尽可能增长缓慢,以容纳更多元素)。
- 如果
-
返回值:
dp.size()
即为最长递增子序列的长度,也就是最多能套娃的信封数量。
复杂度分析
-
时间复杂度: O ( N l o g N ) O(N\,logN) O(NlogN)
- 排序: O ( N l o g N ) O(N\,logN) O(NlogN)
- LIS 的贪心 + 二分: O ( N l o g N ) O(N\,logN) O(NlogN)
-
空间复杂度: O ( N ) O(N) O(N)
- dp 数组的空间开销。
总结
本题通过 排序 + LIS 的经典组合,将二维问题转化为一维问题。关键在于:
- 按宽度升序、高度降序排序,避免同一宽度的信封被重复选择。
- 使用贪心 + 二分优化 LIS,将时间复杂度从 O ( N 2 ) O(N^2 ) O(N2) 降为 O ( N l o g N ) O(N\,logN) O(NlogN)。