LeetCode 300题,最长递增子序列详解
给你一个整数数组nums,找到其中最长严格递增子序列的长度。
子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如[3,4,2,7]是数组[0,3,1,6,2,2,7]的子序列。
子序列是顺序不可以改变,但是可以不连续的。子数组必须得是连续的。
原题目要求最长递增子序列的长度,如果只使用动态规划的话,时间复杂度会是n²,不符合。因此我们需要将动态规划与二分结合。
二分的时间复杂度是logn,结合后的时间复杂度是nlogn。
思想:最长子序列是上升最慢的,我们需要让序列上升的尽可能的慢。使用反证法站名末尾数组最小值的单调性,因此可以使用二分法来查找更新元素的最小值,找到上升最慢的序列。
vector<int> lengthOfLIS(vector<int>& nums){
vector<int> ans;//组里用来存放一些递增子序列
vector<int> maxLen;//存放最长自增子序列,其实就是贪心+二分的dp数组
ans.push_back(nums[0]);//递增子序列一开始存在的元素是nums[0]
maxLen.push_back(1);//初始时最长递增子序列长度为1
for(int i = 1;i<nums.size();i++){
if(nums[i]>ans.back())//单调递增{
ans.push_back(nums[i]);//和初始化的逻辑差不多,就不断的增加最长递增子序列的长度
maxLen.push_back(ans.size());//同上
}
else{
auto pos = lower_bound(ans.begin(),ans.end(),nums[i])-ans.begin();//第一个大于等于nums[i]的位置。
ans[pos] = nums[i];//将pos处的值换成nums[i],pos处位置元素肯定是比nums[i]大的,这样换小了。
maxLen.push_back(pos+1);//长度更改为pos+1,因为0处位置也很重要,所以说加上0.
}
}
//return *max_element(maxLen.begin(),maxLen.end());这个其实就是上面leetcode的要求
//填充最长递增子序列 返回最长递增子序列
int i = nums.size()-1;//i对应于nums
int j = ans.size();//j对应于ans
while(j>0)
{
if(maxLen[i]==j)
{
ans[--j] = nums[i];
}
i--;
}
return ans;
}
lower_bound(begin,end,val)包含在中,它的作用是返回有序数组begin,end中第一个大于等于val的元素的迭代器。
使得最长上升递增子序列的元素变得更加小,这里肯定是nums[i]的值更加小。
关键思想其实就是尽可能的使数据上升的更加慢,在ans内存储的递增子序列,如果新来的数字是递增的,那样的话直接加在后面就可以了。要不然的话需要找到当前元素的lower_bound的位置,之后需要在当前位置加上该位置的值,ans[pos] = nums[i],同时maxLen.push_back(pos+1)。具体看备注。
需要返回最长递增子序列的值,就需要往ans里面加入元素,j是ans里面大小的位置。i是maxLen的位置下标。