2021/3/4
今天正式开始每日一题,今天的题是Leetcode354. 俄罗斯套娃信封问题,在做此题之前先要了解Leetcode300. 最长递增子序列,所以我们先以此题入手。
300题题意为
给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。
子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。
第一个想到的是动态规划,定义dp[i]为以nums[i]为结尾的最长递增子序列,所以转移方程为从0到i遍历每一个j,如果j位置的数大于i位置的数则可以将j位置的数放在i位置并后面长度加一。所以状态转移方程为当nums[j]>nums[i]时,dp[i] = max(dp[i], dp[j] + 1);
这样我们就可以找到最大的dp的值来找到最长的子序列。
c++参考代码
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
int n=nums.size();
if(n==0)
return 0;
vector<int> dp(n,0);
int ans=0;
for(int i=0;i<n;i++)
{
dp[i]=1;
for(int j=0;j<i;j++)
{
if(nums[j]<nums[i])
dp[i]=max(dp[i],dp[j]+1);
}
ans=max(ans,dp[i]);
}
return ans;
}
};
时间复杂度:O(N^2)。空间复杂度:O(N)。
我们发现,每一次更新i都要遍历从0到i,太过浪费时间,考虑优化,这里我们可以使用贪心算法。以贪心思路考虑,当前子序列结尾元素越小,后续可能接在后面的元素就越多,所以我们每更新一个nums[i],要么把它放到最后,要么用它替换前面大的数(为之后的更新做准备),反正我们的最终目的只是子序列的长度最长,在长度不变的情况下子序列的每一个值肯定越小越好。所以我们维护一个数组tail,这样每次更新一个nums[i]会出现以下两种情况:
1.nums[i]>tail的最后一个数,那么将nums[i]放到tail后面,子序列长度加一。
2.nums[i]<tail的最后一个数,那么找到tail里第一个大于(或等于)nums[i]的数,将它变成nums[i],子序列长度不变。(等于时情况一样,替换与否都一样。)
这样我们维护的tail数组就是最长的子序列,tail的长度即为答案。
c++参考代码:
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
int n=nums.size();
if(n==0)
return 0;
vector<int> tail;
tail.push_back(nums[0]);
int end=0;
for(int i=1;i<n;i++)
{
if(nums[i]>tail[end])
{
tail.push_back(nums[i]);
end++;
}
else//这里使用二分查找缩短时间复杂度。
{
int l=0,r=end;
while(l<r)
{
int mid=(l+r)>>1;
if(tail[mid]<nums[i])
l=mid+1;
else
r=mid;
}
tail[l]=nums[i];
}
}
return end+1;
}
};
时间复杂度:O(N log N),空间复杂度:O(N)。
这一题做完后,我们现在有实力来看Leetcode354. 俄罗斯套娃信封问题了,题意为:
给定一些标记了宽度和高度的信封,宽度和高度以整数对形式 (w, h) 出现。当另一个信封的宽度和高度都比这个信封大的时候,这个信封就可以放进另一个信封里,如同俄罗斯套娃一样。
请计算最多能有多少个信封能组成一组“俄罗斯套娃”信封(即可以把一个信封放到另一个信封里面)。(不允许旋转信封。)
首先这一题与之前一题的第一个区别就是消除了子序列的限制,这样我们就可以愉快的进行排序了。其实这一题也就是在上一题的基础上变成了二维的情况,可以考虑将两个维度分开考虑。将一个维度排序后,另一维度就变成了上面那道题,但是要考虑一种特殊情况,如过我们单纯的按照递增排序,排完序的结果为 [(w, h)] = [(1, 1), (1, 2), (1, 3), (1, 4)],这样我们忽略w维度,剩下元素为[1,2,3,4],这样长度为4,但实际上因为w相同长度只能为1。所以为了考虑w维度相同的情况,我们将w维度还是递增排序,w相同时h递减排序,这样相同的w只有一个h会被计算。
同样对应上一题有两种解法。
动态规划c++代码:
class Solution {
public:
int maxEnvelopes(vector<vector<int>>& envelopes) {
if (envelopes.empty()) {
return 0;
}
int n = envelopes.size();
sort(envelopes.begin(), envelopes.end(), [](const auto& e1, const auto& e2) {
return e1[0] < e2[0] || (e1[0] == e2[0] && e1[1] > e2[1]);
});
vector<int> dp(n,1);
int ans=1;
for(int i=1;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);
}
ans=max(ans,dp[i]);
}
}
return ans;
}
};
时间复杂度:O(N^2)。空间复杂度:O(N)。
状态转移c++代码:
class Solution {
public:
int maxEnvelopes(vector<vector<int>>& envelopes) {
if(envelopes.empty())
{
return 0;
}
sort(envelopes.begin(), envelopes.end(), [](const auto& e1, const auto& e2) {
return e1[0] < e2[0] || (e1[0] == e2[0] && e1[1] > e2[1]);
});
vector<int> tail= {envelopes[0][1]};
for(int i=1;i<envelopes.size();i++)
{
int now=envelopes[i][1];
if(now>tail.back())
{
tail.push_back(now);
}
else{
auto it = lower_bound(tail.begin(), tail.end(), now);
*it = now;
}
}
return tail.size();
}
};
时间复杂度:O(N log N),空间复杂度:O(N)。
参考资料
·力扣官方题解
·liweiwei1419《动态规划(包含O (N log N) 解法的状态定义以及解释)》