难度中等1382
给你一个整数数组 nums
,找到其中最长严格递增子序列的长度。
子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7]
是数组 [0,3,1,6,2,2,7]
的子序列。
示例 1:
输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。
示例 2:
输入:nums = [0,1,0,3,2,3]
输出:4
示例 3:
输入:nums = [7,7,7,7,7,7,7]
输出:1
提示:
1 <= nums.length <= 2500
-104 <= nums[i] <= 104
进阶:
- 你可以设计时间复杂度为
O(n2)
的解决方案吗? - 你能将算法的时间复杂度降低到
O(n log(n))
吗?
基本动态规划解法(O(N2)):个dp[i]记录的是以第 i 个数字作为末尾时,前i个数字的最长严格递增子序列长度,因此动态转移方程为:
dp[i]=max(dp[j])+1,其中0≤j<i且num[j]<num[i])
即当 num[i] > num[j] 时,说明num[i] 做为末尾时,可比dp[j] 的末尾大,即长度+1,比较它能够作为末尾的所有情况,找出最大值dp[i]。记录过程中,比较所有的dp[i],其最大值即为结果。
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
vector<int>dp(nums.size(), 1);
int res = 1;
for (int i = 1; i < dp.size(); ++i) {
for (int j = 0; j < i; ++j) {
if (nums[i] > nums[j])
dp[i] = max(dp[i], dp[j] + 1);
}
res = max(res, dp[i]);
}
return res;
}
};
通过二分查找,优化后的动态规划((O(NlogN))):为了进一步优化时间复杂度,修改dp数组的含义为,dp[i]表示在之前查找的结果中,最长严格递增子序列长度为 i 时,队列末尾的最小值。因此,在遍历时,只需要遍历该dp数组,比较num[i]其与不同长度的最小末尾,大于则说明长度应加一。由于这里规定的dp[i]是长度为i的最小末尾,因此dp数组是一个严格单调递增的(反证:假设存在非递增dp[i] > dp[j] , i < j ,例如dp[2] = 8,dp[4] = 5, 由于定义的dp[4]为长度为4的递增序列的末尾值,因此之前的三个数必然小于5,因此长度为2的最小末尾也肯定小于5)。因此,可以通过二分查找找到小于num[i]的dp数组的位置,通过比较其与下一个位置的dp值,更新它。最后,dp数组的大小即为结果。
注:lower_bound和upper_bound同为二分查找算法,其均为找出目标值所处位置,不同在于,lower_bound返回的是 第一个 可插入的位置,而 upper_bound找到的是 最后一个 可插入的位置,举例来说,对于[1,3,5,5,7,8]来说,当val = 5时,lower_bound返回的索引为2,upper_bound返回的索引为4。当val = 4时,lower_bound返回的索引为3,upper_bound返回的索引为3。
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
vector<int>dp;
dp.push_back(INT32_MIN);
dp.push_back(nums[0]);
int res = 1;
for (int i = 1; i < nums.size(); ++i) {
int index = lower_bound(dp.begin(), dp.end(), nums[i]) - dp.begin();
//cout << index << ' '<<i<< ' '<< nums[i]<<endl;
if (index == dp.size() && nums[i]) {
dp.push_back(nums[i]);
}
else {
dp[index] = min(dp[index], nums[i]);
}
}
return dp.size() - 1;
}
};
LIS问题进阶:
难度困难299
给定一些标记了宽度和高度的信封,宽度和高度以整数对形式 (w, h)
出现。当另一个信封的宽度和高度都比这个信封大的时候,这个信封就可以放进另一个信封里,如同俄罗斯套娃一样。
请计算最多能有多少个信封能组成一组“俄罗斯套娃”信封(即可以把一个信封放到另一个信封里面)。
说明:
不允许旋转信封。
示例:
输入: envelopes =[[5,4],[6,4],[6,7],[2,3]]
输出: 3 解释: 最多信封的个数为3, 组合为:
[2,3] => [5,4] => [6,7]。
为满足题意,需要保证找到的信封满足w0<w1<w2<...<wn,h0<h1<h2<...<hn的条件,而当我们对所有信封在一个维度w上排序后,在这个纬度w上就已经满足单调要求,然而由于在w纬度存在相等的情况,因此仍需判断在w纬度是否相等,为此,在 w 值互不相同的前提下,小于等于 ≤ 和小于 < 是等价的,那么我们在排序后,就可以完全忽略 w 维度,只需要考虑 h 维度了。此时,我们需要解决的问题即为:
给定一个序列,我们需要找到一个最长的子序列,使得这个子序列中的元素严格单调递增,即上面所说的LIS问题。
而为了忽略 w 维度,只需要考虑 h 维度,我们可以将 h值作为排序的第二关键字进行降序排序,这样一来,对于每一种w值,其对应的信封在排序后的数组中是按照 h 值递减的顺序出现的,那么这些 h值不可能组成长度超过1的严格递增的序列,即以h纬度生成的单点递增子序列不会存在w相同的信封,满足题意。
对二维数组排序后,就转换为上文的一维LIS问题,代码如下:
int cmp1(const void *a, const void *b)
{
if ((*(vector<int>*)a)[0] == (*(vector<int>*)b)[0] ) {
return (*(vector<int>*)a)[1] < (*(vector<int>*)b)[1]; // 从大到小排序
}
else
return (*(vector<int>*)a)[0] > (*(vector<int>*)b)[0]; // 从小到大排序
}
class Solution {
public:
int res = 0;
int maxEnvelopes(vector<vector<int>>& envelopes) {
vector<int>dp;
qsort(&envelopes[0], envelopes.size(), sizeof(vector<int>(2)), cmp1);
// for (auto i : envelopes)
// cout << i[0] << ' ' << i[1] << ' ' << endl;
dp.push_back(INT32_MIN);
dp.push_back(envelopes[0][1]);
for (int i = 1; i < envelopes.size(); ++i) {
int index = lower_bound(dp.begin(), dp.end(), envelopes[i][1]) - dp.begin();
if (index == dp.size())
dp.push_back(envelopes[i][1]);
else
dp[index] = min(dp[index], envelopes[i][1]);
}
return dp.size() - 1;
}
};
而在不知道这一方法之前,采用的方式为:
class Node {
public:
static bool flag;
int a, b, val;
Node *left;
Node *right;
Node(int x, int y) :a(x), b(y) {
val = 1;
left = right = nullptr;
};
bool operator <(const Node& N)const {
if (flag) {
if (a == N.a)
return b > N.b;
else
return a > N.a;
}
else {
if (b == N.b)
return a > N.a;
else
return b > N.b;
}
}
};
int cmp(const void *a, const void *b)
{
return *(*(Node**)a) < *(*(Node**)b);
}
int cmp1(const void *a, const void *b)
{
if (*(vector<int>*)a == *(vector<int>*)b)
return *(vector<int>*)a > *(vector<int>*)b;
else
return *((vector<int>*)a + 1) > *((vector<int>*)b + 1);
}
bool Node::flag = 1;
class Solution {
public:
int res = 0;
int maxEnvelopes(vector<vector<int>>& envelopes) {
if(envelopes.size()==0)
return 0;
vector<Node*>positive;
for (auto i : envelopes) {
Node *mid = new Node(i[0], i[1]);
positive.push_back(mid);
}
qsort(&positive[0], positive.size(), sizeof(Node*), cmp);
for (auto i : positive)
{
cout << i->a << ' ' << i->b << ' ' << i->val << ' ' << (i->left == nullptr) << endl;
}
int record = positive.size() - 1;
for (int i = record - 1; i >= 0; --i) {
if (positive[i]->a < positive[i + 1]->a)
record = i + 1;
if (positive[i]->a < positive[record]->a) {
int k = 0, yu = positive[record]->a;
bool judge = 0; //判断是否存在b小且left==null的节点
while (record + k < positive.size()) {
if (positive[record + k]->b > positive[i]->b) {
positive[i]->left = positive[record + k];
cout << i << ' ' << record + k << endl;
break;
}
else if(positive[record + k]->left==nullptr){
judge = 1; //说明a相同的这层存在b小且left==null的节点
}
k++;
if (record + k < positive.size()) {
if (positive[record + k]->a > yu) {
yu = positive[record + k]->a;
if (judge)
break;
}
}
}
}
}
Node::flag = 0;
qsort(&positive[0], positive.size(), sizeof(Node*), cmp);
for (auto i : positive)
{
cout << i->a << ' ' << i->b << ' ' << i->val << ' ' << (i->left == nullptr) << endl;
}
record = positive.size() - 1;
int res = 1;
for (int i = record - 1; i >= 0; --i) {
if (positive[i]->b < positive[i + 1]->b)
record = i + 1;
if (positive[i]->b < positive[record]->b) {
int k = 0, yu = positive[record]->b;
bool judge = 0; //判断是否存在b小且left==null的节点
while (record + k < positive.size()) {
if (positive[record + k]->a > positive[i]->a) {
positive[i]->right = positive[record + k];
positive[i]->val = max(positive[i]->val, positive[record + k]->val + 1);
res = max(res,positive[i]->val);
cout << i << ' ' << record + k << ' ' << positive[i]->val << endl;
break;
}
else if (positive[record + k]->right == nullptr) {
judge = 1; //说明a相同的这层存在b小且left==null的节点
}
k++;
if (record + k < positive.size()) {
if (positive[record + k]->b > yu) {
yu = positive[record + k]->b;
if (judge)
break;
}
}
}
}
}
return res;
}
};
难度困难38收藏分享切换为英文接收动态反馈
堆箱子。给你一堆n个箱子,箱子宽 wi、深 di、高 hi。箱子不能翻转,将箱子堆起来时,下面箱子的宽度、高度和深度必须大于上面的箱子。实现一种方法,搭出最高的一堆箱子。箱堆的高度为每个箱子高度的总和。
输入使用数组[wi, di, hi]
表示每个箱子。
示例1:
输入:box = [[1, 1, 1], [2, 2, 2], [3, 3, 3]] 输出:6
示例2:
输入:box = [[1, 1, 1], [2, 3, 4], [2, 6, 7], [3, 4, 5]] 输出:10
提示:
- 箱子的数目不大于3000个。
这道题作为三维的LIS问题,其要求的结果与上述不同,不是要求序列长度最长,二是要求某一纬度(高度)求和最大,因此不能使用O(nlogn)复杂度的方法,而是使用基本的O(n2)的方法。如下:
bool cmp(vector<int>a, vector<int>b) {
if (a[2] == b[2]) {
if (a[1] == b[1]) {
return a[0] > b[0];
}
else
return a[1] > b[1];
}
else
return a[2] > b[2];
}
class Solution {
public:
int pileBox(vector<vector<int>>& box) {
sort(box.begin(), box.end(), cmp);
vector<int>dp(box.size() + 1, 0);
int res = 0;
for (int i = 0; i < dp.size(); ++i) {
dp[i] = box[i][2];
for (int j = 0; j < i; ++j) {
if (box[j][2] > box[i][2] && box[j][1] > box[i][1] && box[j][0] > box[i][0])
dp[i] = max(dp[i], dp[j] + box[i][2]);
}
res = max(res, dp[i]);
}
return res;
}
};