题目描述
给定一个无序的整数数组,找到其中最长上升子序列的长度。
示例:
输入: [10,9,2,5,3,7,101,18]
输出: 4
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。
说明:
- 可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
- 你算法的时间复杂度应该为 O(n2) 。
进阶: 你能将算法的时间复杂度降低到 O ( n l o g n ) O(n log n) O(nlogn) 吗?
解题思路
这是一个子序列问题,子序列问题基本都可以归化为“动态规划”问题。
-
动态规划(四要素:状态表示、初始化、状态转移方程、返回值)
- 使用数组
cell保存每步子问题的最优解,cell[i]代表以nums[i]为结尾的最长上升子序列长度 - 求解
cell[i]时,向前遍历找出比i元素小的元素j,状态转移方程为:cell[i] = max(cell[i], cell[j] + 1)。(0 <= j < i && cell[j] < cell[i]) - 初始化:
cell[0] = 1(一定要把整个数组全部初始化为1) - 迭代过程中不断更新最大值,返回值为迭代过程中的最大值。两次遍历数组,时间复杂度 O ( n 2 ) O(n^2) O(n2)。
- 使用数组
-
动态规划 + 二分:进阶要求时间复杂度要求 O ( n l o g n ) O(nlogn) O(nlogn),立马想到二分。这个思路比较巧妙,很具小巧思。
- 新建数组
cell,用于保存最长上升子序列(保存的是序列,不是状态表示了)。对原序列进行遍历,将每位元素二分插入cell中。 - 如果
cell中元素都比它小,将它插到最后。 - 否则,用它覆盖掉大于等于它的元素中最小的那个。
总之,思想就是让
cell中存储比较小的元素。这样,cell未必是真实的最长上升子序列,但长度是对的。 - 新建数组
参考代码
动态规划
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
int length = nums.size();
if(length == 0)
return 0;
if(length == 1)
return 1;
int res = 1;
int dp[length]; // 用变量初始化的数组,不能在定义时初始化。
for(int i = 0; i < length; i++) // 一定要全部初始化为 1(细节)
dp[i] = 1;
for(int i = 1; i < length; i++){
for(int j = i-1; j >= 0; j--){
if(nums[j] < nums[i])
dp[i] = max(dp[i], dp[j] + 1);
}
res = max(res, dp[i]);
}
return res;
}
};
动态规划 + 二分(之前写的二分,不好,需要判断很多边界条件)
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
int length = nums.size();
if(length == 0)
return 0;
if(length == 1)
return 1;
vector<int> res;
res.push_back(nums[0]);
for(int i = 1; i < length; i++){
if(nums[i] > res.back()){
res.push_back(nums[i]);
continue;
}
int low = 0, high = res.size() - 1;
int pos = binarySearch(res, low, high, nums[i]);
if(pos == -1) continue;
res[pos] = nums[i];
}
return res.size();
}
int binarySearch(vector<int>& res, int low, int high, int k){
while(low <= high){
int mid = (low + high) >> 1;
if(mid != 0 && res[mid] > k && res[mid-1] < k)
return mid;
if(mid == 0 && res[mid] > k)
return mid;
if(res[mid] < k)
low = mid + 1;
else if(res[mid] == k) // debug时加的,即已经存在某元素,则不再改变res
return -1;
else
high = mid - 1;
}
return -1;
}
};
按照模板写的二分(推荐)
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
int length = nums.size();
if(length == 0)
return 0;
if(length == 1)
return 1;
vector<int> res;
res.push_back(nums[0]);
for(int i = 1; i < length; i++){
if(nums[i] > res.back()){
res.push_back(nums[i]);
continue;
}
int left = 0, right = res.size() - 1;
int pos = binarySearch(res, left, right, nums[i]);
res[pos] = nums[i];
}
return res.size();
}
// 首先要明确我们的目的是要找第一个“大于等于k”的元素的下标
int binarySearch(vector<int>& res, int left, int right, int k){
while(left < right){
int mid = left + ((right - left) >> 1);
if(res[mid] < k)
left = mid + 1;
else
right = mid;
}
return left;
}
};
本文介绍了解决最长上升子序列问题的两种算法:动态规划和动态规划结合二分搜索。动态规划通过维护一个数组来记录每个元素结尾的最长上升子序列长度,而动态规划结合二分搜索则利用二分搜索优化了查找过程,达到了O(nlogn)的时间复杂度。
1089

被折叠的 条评论
为什么被折叠?



