微软和谷歌的几个大佬组织了一个面试刷题群,可以加管理员VX:sxxzs3998(备注掘金),进群参与讨论和直播
1. 题目
给你一个二维整数数组 envelopes ,其中 ,表示第 个信封的宽度和高度。
当另一个信封的宽度和高度都比这个信封大的时候,这个信封就可以放进另一个信封里,如同俄罗斯套娃一样。
请计算 最多能有多少个 信封能组成一组“俄罗斯套娃”信封(即可以把一个信封放到另一个信封里面)。
注意:不允许旋转信封。
示例 1:
输入:envelopes = [[5,4],[6,4],[6,7],[2,3]]
输出:3
解释:最多信封的个数为 3, 组合为: [2,3] => [5,4] => [6,7]。
示例 2:
输入:envelopes = [[1,1],[1,1],[1,1]]
输出:1
2. 解析
这道题可以看成是最长递增子序列(Longes Increasing Subsequence,简写为 LIS)的一个变种,因为很显然,每次合法的嵌套是大的套小的,相当于找一个最长递增的子序列,其长度就是最多能嵌套的信封个数。但是难点在于,标准的 LIS 算法只能在数组中寻找最长子序列,而我们的信封是由 (w, h) 这样的二维数对形式表示的,如何把 LIS 算法运用过来呢?
读者也许会想,通过 计算面积,然后对面积进行标准的 LIS 算法。但是稍加思考就会发现这样不行,比如 1 × 10 大于 3 × 3,但是显然这样的两个信封是无法互相嵌套的。
那既然都说是变种,是否可以仍然对其进行排序呢?
先对宽度 进行升序排序,如果遇到 相同的情况,则按照高度 降序排序。之后把所有的 作为一个数组,在这个数组上计算 LIS 的长度就是答案。
然后在 上寻找最长递增子序列:
这个子序列就是最优的嵌套方案。这个解法的关键在于,对于宽度 w 相同的数对,要对其高度 h 进行降序排序。因为两个宽度相同的信封不能相互包含的,逆序排序保证在 w 相同的数对中最多只选取一个。
class Solution {
public:
int maxEnvelopes(vector<vector<int>>& envelopes) {
int n = envelopes.size();
// 首先执行排序,按照宽度排序,小的在前大的在后
sort(envelopes.begin(), envelopes.end(), [](vector<int>& a, vector<int>& b){
if(a[0] == b[0]){
// 对于宽度相等的信封,根据高度逆序,大的在前小的在后
return a[1] > b[1];
}
return a[0] < b[0];
});
// 预开空间,初始值为排序后第一个信封的高度
vector<int> dp(1, envelopes[0][1]);
int ans = 0;
// 计算最长上升子序列
// 第0个元素已默认放入dp,因此从1开始遍历
for(int i = 1; i < n; i++){
// 搜索合适的更新位置,使用二分模板
// 额外引入一个index来记录满足条件合法的值
// 有的人的模板中,只有l和r两个变量,但是那个边界条件我总是记不住
// 引入一个新的变量,个人感觉逻辑更明朗
int l = 0, r = dp.size() - 1;
int index = -1;
while(l <= r){
// mid这里用l加一半的形式,不容易溢出int
int mid = l + (r - l) / 2;
if(dp[mid] >= envelopes[i][1]){
// 我们要找的是dp数组中第一个大于等于当前h的位置
// 因此在这里更新index值
index = mid;
r = mid - 1;
}
else{
l = mid + 1;
}
}
if(index == -1){
dp.emplace_back(envelopes[i][1]);
}
else{
dp[index] = envelopes[i][1];
}
}
return dp.size();
}
};
-
时间复杂度 :排序需要 ,遍历信封列表需要 ,计算每一个信封插入位置需要
-
空间复杂度 :dp列表占用 空间。
微软和谷歌的几个大佬组织了一个面试刷题群,可以加管理员VX:sxxzs3998(备注掘金),进群参与讨论和直播