leetcode 300.Longest Increasing Subsequence(最长递增子序列) ,网上多是动态规划,复杂度为O(n^2)算法;本文设计一个O(nlogn)算法,即维护最小的最长子序列
题目:
题目大意:求一个无序数组的最长递增子序列。这里的子序列,不要求是连续子序列。比如文中[10,9,2,5,3,7,101,18]最长子序列为[2,3,7,101],长度为4。
最简单的思路,动态规划,纪录之前所有位置的最长长度,复杂度为O(n^2)。
代码如下(c++,O(n^2),可AC):
int lengthOfLIS(vector<int>& nums) {
if(nums.size()<=1) return nums.size();
vector<int> b(nums.size(),1);
int res=0;
for(int i=1;i<nums.size();i++){
for(int j=0;j<i;j++){
if(nums[i]>nums[j])
b[i]=max(b[i],b[j]+1);
}
res=max(res,b[i]);
}
return res;
}
显然,有更好的办法。这个办法思路是这样的:对于一个子序列,如[10,9,2,5,3,7,101,18],[2,3,7,101],[2,5,7,101],[2,3,7,18],[2,5,7,18]都是它的最长子序列,但我们没必要纪录这么多子序列,我们只需纪录对我们最有用的子序列,显然其中最小的子序列最有用,比如如果我再在[10,9,2,5,3,7,101,18]插入20,显然[2,3,7,18]最好处理,它后面直接插入19即可,得[2,3,7,18,20]。你可能看一下下面具体例子,可能更好理解。
维护一个最长递增子序列vector<int> v,且里面的数字最小,举例[10,9,2,5,3,7,101,18]:处理第一个数10,则v=[10];处理第二个数9,9<=10,则更新v=[9];处理第三个数2,2<=9,更新v=[2];处理第四个数5,5>2,插入v尾部,v=[2,5];处理第五个数3,3<=5,且3>2,更新v=[2,3];处理第六个数7,7>3,插入v尾部,v=[2,3,7];处理下一个数101,101>7,插入尾部,更新v=[2,3,7,101];处理下一个数18,18<=101,且18>7(找出18更新的位置,即更新大于等于18,且最接近18的数),更新v=[2,3,7,18]。结束。主意:一个数如果大于v的尾部,则插入尾部;否则更新v,该数更新的位置为大于等于该数且最接近该数的数。这样的话,一个数插入复杂度为1,更新复杂度为logn。这样,总的复杂度为O(nlogn)。
代码如下(c++,O(nlogn),可AC):
int binarysearch(vector<int>& v, int x){//二分查找出x更新的位置
int left=0, right=v.size()-1, mid=0;
while(left<right){
mid = (left+right)/2;
if(x==v[mid])
return mid;
else if(x>v[mid])
left=mid+1;
else if(x<v[mid])
right=mid;
}
return right;
}
int lengthOfLIS(vector<int>& nums) {
if(nums.size()<=1) return nums.size();
vector<int> v;//最长递增序列
v.push_back(nums[0]);
for(int i=1;i<nums.size();i++){
if(nums[i]>v.back()){ //nums[i]大于数组中最大数,则插入尾部
v.push_back(nums[i]);
}
else{ //将nums[i]更新到已有递增序列
int idx =binarysearch(v,nums[i]);
v[idx] = nums[i];
}
}
return v.size();
}