问题描述:
给定数组,[3,1,2,6,4,5,10,7],求最长上升子序列,要求时间复杂度 O(nlogn)
思路:
方法一:传统方法,动态规划
第一步,定义数组含义,dp[i]表示以i结尾的最长上升子序列
第二步,数组初始化,因为单个元素情况下,数组的最长上升子序列就是1,所以数组初始化为1
第三步,确定状态转移方程,第i个元素,和之前的元素j比较,如果i元素大于j元素,则dp[i] = max(dp[i],dp[j]+1);
代码:
int length_of_LIS(vector<int> array){
int n = array.szie();
vector<int> dp(n,1);
int res = 0;
for(int i=0;i<n;i++){
for(int j=0;j<i;j++){
if(array[i]>array[j]){
dp[i] = max(dp[i],dp[j]+1);
}
}
res = max(res,dp[i]);
}
return res;
}
分析:时间复杂度是O(n*n),明显是不符合题意的,会超时!!!
方法二:贪心+二分
模拟单调栈的一个思路,分为下面两种情况:
- 如果当前的元素,大于栈顶的元素,则直接将当前元素放入栈中;
- 如果当前的元素,小于栈顶的元素,则到单调栈中,去寻找第一个大于等于当前元素的位置,并将它替换掉(贪心思想,长度没变,但是潜力增大了)
最后,单调栈中的元素,即为最长上升子序列
int binarySearch(vector<int> sta,int num){ //也可借助lower_bound实现
int l = 0;
int r = sta.size()-1;
while(l<=r){
int mid = l+(r-l)/2;
if(sta[mid]>num){
r = mid-1;
}else{
l = mid+1;
}
}
return l;
}
int length_of_LIS(vector<int> arrs){
vector<int> sta;
int pos = 0;
for(auto&num:arrs){
if(sta.empty()){
sta.emplace_back(num);
continue;
}
if(num>sta.back()){
sta.emplace_back(num);
}else{
//int rep = lower_bound(sta.begin(),sta.end(),num) - sta.begin();
int rep = binarySearch(sta,num);
sta[rep] = num;
}
}
return sta.size();
}
分析:二分查找是O(logn),外层for循环时O(n),所以总的时间复杂度为:O(nlogn)