题目一
给定一个无序的整数数组,找到其中最长上升子序列的长度。
示例:
输入: [10,9,2,5,3,7,101,18]
输出: 4
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。
说明:
- 可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
- 你算法的时间复杂度应该为 O ( n 2 ) O(n^{2}) O(n2) 。
进阶: 你能将算法的时间复杂度降低到 O ( n l o g n ) O(n log n) O(nlogn) 吗?
方法一:动态规划
解题思路
-
思考定义状态:每个元素都可能作为最后最长递增子序列的末尾。
-
定义 d p [ i ] dp[i] dp[i]为以数组中第i个元素为结尾的最长递增子序列的长度。
-
初始化:由于每个元素单独都可以作为一个递增子序列,因此 d p dp dp 数组全部初始化为1。
-
状态转移: d p [ i ] = n u m s [ i ] + m a x ( d p [ i − 1 ] , d p [ i − 2 ] , . . . ) dp[i] = nums[i] + max(dp[i - 1], dp[i - 2], ...) dp[i]=nums[i]+max(dp[i−1],dp[i−2],...)。注: m a x max max 中的原数组元素必须严格小于 n u m s [ i ] nums[i] nums[i],这样 n u m s [ i ] nums[i] nums[i]才能够接在他们后面构成最长递增子序列。
-
最终结果:就是遍历 d p dp dp 数组找到其中的最大值。
代码一
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
if(nums.size() < 2){
return nums.size();
}
//每次的一个新数,都把他当作结尾,找以他结尾的最长字串,他需要对前面的所有dp进行遍历
vector<int> dp(nums.size(), 1);
int maxLen = 1;
for(int i = 1; i < nums.size(); ++i){
for(int j = 0; j < i; ++j){
if(nums[j] < nums[i]){
dp[i] = max(dp[i], dp[j] + 1);
}
}
maxLen = max(maxLen, dp[i]);
}
return maxLen;
}
};
时间复杂度: O ( n 2 ) O(n^2) O(n2)。
空间复杂度: O ( n ) O(n) O(n)。
方法二:动态规划,二分查找
解题思路
- 重新定义状态: t a i l [ i ] tail[i] tail[i]代表长度为 i + 1 i+1 i+1 的递增子序列的最小值
- 每次加入一个元素:若该元素大于维护的数组中的元素,则将该元素放入数组。否则,查找该元素可以作为的递增子序列的最小值,替换元素。
- 在查找替换位置的时候,因为 t a i l tail tail 中的元素是递增的,所以可以采用二分查找来插入元素。
代码二
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
if(nums.size() < 2){
return nums.size();
}
/*
方法二:重新定义状态:dp[i]代表长度为i+1的递增子序列的最小值
每次加入一个元素:若该元素大于维护的数组中的元素,则将该元素放入数组
否则,查找该元素可以作为的递增子序列的最小值,插入
*/
vector<int> tail;
int end = 0; //标识序列结尾
tail.push_back(nums[0]);
for(int i = 1; i < nums.size(); ++i){
if(nums[i] > tail[end]){
tail.push_back(nums[i]);
end++;
}
else{
//查找该元素可以作为的递增子序列最小值的插入位置
//即就是查找【0,end】中第一个大于等于tail[end]的位置
int left = 0;
int right = end;
while(left < right){
int mid = left + (right - left) / 2;
if(tail[mid] >= nums[i]){
right = mid;
}
else{
left = mid + 1;
}
}
tail[left] = nums[i];
}
}
return end + 1;
}
};
时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)
空间复杂度: O ( n ) O(n) O(n)
题目二
有个马戏团正在设计叠罗汉的表演节目,一个人要站在另一人的肩膀上。出于实际和美观的考虑,在上面的人要比下面的人矮一点且轻一点。已知马戏团每个人的身高和体重,请编写代码计算叠罗汉最多能叠几个人。
示例:
输入:height = [65,70,56,75,60,68] weight = [100,150,90,190,95,110]
输出:6
解释:从上往下数,叠罗汉最多能叠 6 层:(56,90), (60,95), (65,100), (68,110), (70,150), (75,190)
提示:
- height.length == weight.length <= 10000
解题思路
- 题目给出两个维度,两个维度上都满足严格递增的要求才可以叠上去。
- 考虑需要严格递增,先按照 h e i g h t height height 升序排序,同时 h e i g h t height height 相同的人按照 w e i g h t weight weight 降序排序。
- 这样排序下来,直接在数组中查找关于 w e i g h t weight weight 的最长递增子序列就能得到答案。
- 一个升序,一个降序的是与我们的解决方法相关(我们在排序之后,直接查找体重的最长递增子序列就能得到答案),如: h e i g h t = [ 3 , 2 , 2 , 3 , 1 , 6 ] , w e i g h t = [ 7 , 3 , 5 , 6 , 2 , 10 ] height = [3, 2, 2, 3, 1, 6],weight = [7,3,5,6,2,10] height=[3,2,2,3,1,6],weight=[7,3,5,6,2,10]。两个都升序的排序结果是 h e i g h t = [ 1 , 2 , 2 , 3 , 3 , 6 ] , w e i g h t = [ 2 , 3 , 5 , 6 , 7 , 10 ] height = [1, 2, 2, 3, 3, 6],weight = [2,3,5,6,7,10] height=[1,2,2,3,3,6],weight=[2,3,5,6,7,10],直接查找体重的最长递增子序列结果是 6 6 6,可以看到,同一身高内部可能存在体重的递增。因此,会被加入结果。而使用身高升序,体重降序,排序结果为 h e i g h t = [ 1 , 2 , 2 , 3 , 3 , 6 ] , w e i g h t = [ 2 , 5 , 3 , 7 , 6 , 10 ] height = [1, 2, 2, 3, 3, 6],weight = [2,5,3,7,6,10] height=[1,2,2,3,3,6],weight=[2,5,3,7,6,10],可以看到体重的最长递增子序列结果是 4 4 4。
代码
class Solution {
public:
int bestSeqAtIndex(vector<int>& height, vector<int>& weight) {
//构造最长递增序列,用身高升序排序,同时体重降序排序,然后在体重中查找最长递增子序列
vector<Person> persons;
for(int i = 0; i < height.size(); ++i){
persons.push_back(Person(height[i], weight[i]));
}
sort(persons.begin(), persons.end());
//然后用一个数组dp[i]表示长度为i+1长度的递增子序列的结尾最小值
vector<int> dp;
dp.push_back(persons[0].weight);
int end = 0;
for(int i = 1; i < persons.size(); ++i){
if(persons[i].weight > dp[end]){
dp.push_back(persons[i].weight);
++end;
}
else{
//查找第一个大于等于persons[i].weight的位置
int left = 0;
int right = end;
while(left < right){
int mid = left + (right - left) / 2;
if(dp[mid] >= persons[i].weight){
right = mid;
}
else{
left = mid + 1;
}
}
dp[left] = persons[i].weight;
}
}
return end + 1;
}
private:
struct Person{
int height;
int weight;
Person(int _height, int _weight): height(_height), weight(_weight){}
bool operator < (const Person &person) const{
if(height != person.height){
return height < person.height;
}
else{
return weight > person.weight;
}
}
};
};
时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)
空间复杂度: O ( n ) O(n) O(n)
题目三
堆箱子。给你一堆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个。
解题思路
- 与马戏团叠罗汉相同的思路,只不过这里的约束变成了三维,只有三个方向都满足严格递增,才能选择此时的箱子。实际就是找第一维固定情况下的最长递增子序列,只不过这个子序列需要满足两个条件的约束。
- 用 d p [ i ] dp[i] dp[i] 表示以i结尾的最长递增子序列的高度最大值。
- 状态转移: d p [ i ] = m a x ( d p [ i ] , d p [ j ] + b o x [ i ] [ 2 ] ) dp[i] = max(dp[i], dp[j] + box[i][2]) dp[i]=max(dp[i],dp[j]+box[i][2]),其中 j j j 取小于等于 i i i 的全部值。
- 最终结果为 d p dp dp 数组中的最大值。
代码
class Solution {
public:
int pileBox(vector<vector<int>>& box) {
if(box.size() < 2){
return box.size();
}
//实际就是找第一维固定情况下的最长递增子序列,只不过这个子序列需要满足两个条件的约束
//用dp[i]表示以i结尾的最长递增子序列的高度最大值
sort(box.begin(), box.end(), cmp);
int n = box.size();
vector<int> dp(n, 0);
dp[0] = box[0][2];
int maxHeight = dp[0];
for(int i = 1; i < n; ++i){
//向前寻找满足条件的
dp[i] = box[i][2];
for(int j = 0; j < i; ++j){
if(box[j][0] < box[i][0] && box[j][1] < box[i][1] && box[j][2] < box[i][2]){
dp[i] = max(dp[i], dp[j] + box[i][2]);
}
}
maxHeight = max(maxHeight, dp[i]);
}
return maxHeight;
}
private:
static bool cmp(vector<int> &a, vector<int> &b){ //较短较窄较低的箱子排在前面方便选择
return a[2] < b[2]; //没必要写好几个排序:只需要按照一个维度排序就行,因为在计算每一个元素作为序列末尾元素时都需要满足三个维度上严格递增
}
};
时间复杂度: O ( n 2 ) O(n^{2}) O(n2)
空间复杂度: O ( n ) O(n) O(n)