300. 最长上升子序列




 



目录


  • 题目描述
  • 方法1
  • 方法2


 

题目描述

给定一个无序的整数数组,找到其中最长上升(这里是严格上升)子序列的长度。

示例:

输入: [10,9,2,5,3,7,101,18]

输出: 4

解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。

说明:

可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。

你算法的时间复杂度应该为 O(n2) 。

进阶: 你能将算法的时间复杂度降低到 O(nlogn) 吗?

方法1

暴力dp算法,时间复杂度o(n^2)

class Solution {
public:
int lengthOfLIS(vector<int>& nums) {

int n = nums.size();
if(n == 0)
return 0;
int ret = 0;
//dp[i]含义:以nums[i]为结尾的最长子序列长度
vector<int> dp(n,1);
for(int i = 0;i < n;i++)//流式处理
{
for(int j = 0;j < i;j++)
{
if(nums[i] > nums[j])
dp[i] = max(dp[i],dp[j]+1);
}
if(dp[i] > ret)
ret = dp[i];
}
return ret;
}
};


方法2

上面的方法之所以复杂度为o(n2),是因为当我们处理当前位置时,需要以线性的复杂度去搜索dp中保存的中间信息,要想将复杂度缩小到o(nlogn)

我们需要将对dp进行搜索的复杂度降低到o(logn),为此我们想到了有序数组的折半搜索,这需要我们改变dp的定义,为了达到快速搜索的目的,我们需要将原dp中保存的信息进一步精炼提纯整理转化,dp[i]含义变为:

dp[i]: 当前位置之前所有长度为i+1的递增子序列中, 最小的那个序列尾数.

由定义知dp数组必然是一个递增数组(长度为i+1的递增子序列由一个长度为i的递增子序列+一个大于这个长度为i的子序列的尾数的元素组成)

令ret = dp.size()

对数组进行迭代, 依次判断每个数num将其插入dp数组相应的位置:

  1. num > dp[ret-1], 表示num比所有已知递增序列的尾数都大,
    将num添加入dp数组尾部
  2. dp[i-1] < num < dp[i], 只更新相应的dp[i]
    思考查找的最后一步的情况(分为由两个元素时,进行r = mid(此时mid=l)-1操作,
    跳出搜索,应该插入更新的位置为l;由一个元素时,或者发生r = mid(此时mid=l)-1操作,
    跳出搜索,应该插入更新的位置为l;由一个元素时,或者发生l = mid(此时mid=l)+1操作,
    跳出循环,应该插入更新的位置为更新后的l;综上所述,无论哪种情况跳出搜索,最后应该
    插入更新的位置都是l),决定num的插入位置(是用l还是r)
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {

int n = nums.size();
if(n == 0)
return 0;
int ret = 0;
vector<int> dp;
for(int i = 0;i < n;i++)
{
if(ret == 0 || dp[ret-1] < nums[i])
{
dp.push_back(nums[i]);
ret++;
}
else
{
bool find_success = false;
int l = 0;
int h = ret-1;
while(l <= h)
{
int mid = l+(h-l)/2;
if(dp[mid] == nums[i])
{
find_success = true;
break;
}
else if(dp[mid] < nums[i])
l = mid+1;
else
h = mid-1;
}
if(!find_success)
dp[l] = nums[i];
}

}
return ret;
}

};