1、首先,用一个数组mins去记录各个长度子序列的最小结尾数。
用cnt记录mins的最大下标,也就是当前最长子序列的长度减一;
那么一开始肯定有cnt=0,mins[0]=nums[0];
然后去遍历数组nums,在数组mins中用二分找到大于等于nums[i]的数的最小值,然后替换它,如果找到的数为mins数组中最后一个数mins[cnt],且nums[i]>mins[cnt],则mins[++cnt]=nums[i]。
也就是最长的子序列长度加一,因为nums[i]比当前最长的子序列的数的最大值还要大,所以nums[i]可以加入到最长子序列中,并且成为新的最长子序列的结尾数。
解释一下为什么这样子替换
比如有一个序列 1 2 3 5 4 5
那么则有初始的mins数组 :1
然后判断2,由于它大于1,所以mins数组变成了:1 2
然后再判断3,5,同理,mins变成了:1 2 3 5
然后判断4,这里找到的大于4的最小值为5,所以替换5,为什么替换呢,因为5表示的是子序列长度为4的子序列的最小结尾数,那么相比于5,4比5更小。
替换成4,那么在后面又遇到大于等于5的数,我们的最大子序列长度就可以加一,但是如果保留5,不替换,那么在后面就得找到大于等于6的数,最大子序列长度才能加一,所以显然替换成4更容易找打可以增长最大子序列的数。
最后输出cnt+1,就是我们所求的最长单调子序列的长度。
顺便解释一下,为什么替换后一定存在以其为最后一个数且其下标加一为该子序列长度,这样的子序列。
比如此时cnt=4,也就是当前最长单调子序列长度为5,如果对nums[i]进行二分查找结果为cnt,且mins[cnt]>=nums[i],所以用nums[i]替换mins[cnt],那么是否存在这样的子序列呢,是必然存在的,因为我们查找的结果是大于等于nums[i]的最小值,也就是说mins[cnt-1]必然是小于nums[i]的,那么以mins[cnt-1]结尾的长度为cnt的单调子序列后面加上一个数nums[i],就是一个以nums[i]结尾的长度为cnt+1的单调子序列。
同理进行后面的所有替换都是成立的。
进行空间优化,由于遍历nums数组的下标指针i大于等于cnt的,并且已经访问过的元素对后面的结果不再造成影响,所以可以直接用nums数组当做mins数组去存储相应的结尾数。以此减少另外开辟空间的浪费。
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
int thesize=nums.size();
int cnt=0,l,r,mid;
for(int i=0;i<thesize;++i)
{
l=0,r=cnt;
while(l<r)
{
mid=l+(r-l)/2;
if(nums[mid]>nums[i]) r=mid;
else if(nums[mid]<nums[i]) l=mid+1;
else
{
l=mid;
break;
}
}
if(l==cnt&&nums[i]>nums[l]) nums[++cnt]=nums[i];
else nums[l]=nums[i];
}
return cnt+1;
}
};