给定一个无序的整数数组,找到其中最长上升子序列的长度。
实例:
输入: [10,9,2,5,3,7,101,18]
输出: 4
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。
说明:
可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
你算法的时间复杂度应该为 O(n^2) 。
进阶:你能将算法的时间复杂度降低到 O(n log n) 吗?
很容易想到动态规划算法,可以达到
的时间复杂度。
采用自底向上的动态规划。
状态方程:
f(x) = 以x为起点的最长子序列;
f(x) = 1 + f(y), y > x;
f(x) = 1, x 大于它后面的所有元素;
第一层循环遍历数组,第二层循环找到比当前元素大的元素,再依次比较得出最长子序列。所以时间复杂度为
。
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
if (nums.empty()) return 0;
int numsLen = nums.size(), maxLen = 1;
vector<int> dptable(numsLen, 1);
for (int i = numsLen - 2; i >= 0; i--){
int temp = i + 1;
while (temp < numsLen){
if (nums[i] < nums[temp]){
dptable[i] = max(dptable[i], dptable[temp] + 1);
}
temp++;
}
maxLen = max(maxLen, dptable[i]);
}
return maxLen;
}
};
如果使用贪心的思路,可以实现O(nlogn)时间复杂度的算法
思路:如果我们要使上升子序列尽可能的长,则我们需要让序列上升得尽可能慢,因此我们希望每次在上升子序列最后加上的那个数尽可能的小。
维护一个数组d[i],表示长度为i的最长上升子序列的末尾元素的最小值,用len记录目前最长上升子序列的长度,起始时len为1,d[1] = nums[0]。易证d[i]是关于i的递增序列。
算法流程:
len初始化为1,从前往后遍历数组nums,在便利到nums[i]时:如果nums[i]>d[len],则直接加入到d数组末尾,并更新len=len+1;否则,在d数组1到len的部分进行二分查找(因为是有序数组所以可以进行二分查找),找到第一个比nums[i]小的数d[k],并更新d[k+1] = nums[i];
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
int len = 1, n = (int)nums.size();
if (0 == n){
return 0;
}
vector<int> d(n+1);
d[len] = nums[0];
for (int i = 1; i < n; i++){
if (nums[i] > d[len]){
d[++len] = nums[i];
}
else{ // 进行二分查找,找到第一个比nums[i]小的数d[k];
// 如果找不到说明所有的数都比nums[i]大,此时要更新d[1],将pos设为0
int left = 1, right = len, pos = 0;
while (left <= right){
int mid = (left + right) / 2;
if (d[mid] < nums[i]){
pos = mid;
left = mid + 1;
}
else {
right = mid - 1;
}
}
d[pos + 1] = nums[i];
}
}
return len;
}
};