0x01.问题
具体问题可参考上篇博客,本文主要讲述另一种思路:最长上升子序列--从数学归纳到动态规划
0x02.(贪心+二分+动态规划)思路体现
在上篇博客中,主要是运用动态规划的思路去解决这个问题,时间复杂度为O(n^2)
现在,我们来考虑如何使用更加巧妙的二分法来解决(含贪心和动态规划的思路)
贪心的思路体现:
所谓贪心就是贪,追求每一步都能找到最合适的解,然而在大多数情况下,这是不现实的,所有我们就要退而求其次,找到尽可能好的解。我们首先考虑一下简单的
贪心,如果要使得上升的子序列尽可能长,要满足什么条件?
一个子序列是不断上升的, 要它尽可能的长一点,那么最好的办法就是让它升的慢一点,升的越慢,它就可能越长。
也就是说,如果已经得到的上升子序列的结尾的数越小,遍历的时候后面接上一个数,就会有更大的可能性构成一个更长的上升子序列。
针对贪心的思路去改变动态规划:
我们为了达到这个贪心的思想,我们需要改变动态规划的相应构造。
第一个就是状态:
既然结尾越小越好,我们可以记录在长度固定的情况下,结尾最小的那个元素的数值。
定义状态:tail[i] 表示长度为 i+1 的所有上升子序列的结尾的最小值。
例如:对于序列[10,9,2,5,3,7,56],tails[0]=2,tails[1]=3,tails[2]=5,tails[3]=7,tails[4]=56
这个数组是严格升序的,可以用数学方法证明,不过我们可以简单的理解一下,我们在每一次确定tail里面的一个值的时候,肯定要对之前的不断更新,才能保证它是长度为i+1的所有上升子序列的结尾的最小值。
第二个是状态转移方程:
我们首先想一下,对于这个状态,我们最终的答案应该从哪得到,其实就是tail数组的长度,所以我们的目的就是要不断的去产生tail数组里面的值。
我们每遇到一个nums的值,就要开始更新tails的值,因为这个nums的值可能对tails里面的任何一个值产生影响。
这里需要分情况讨论一下 。
- 如果nums的这个值,比tails当前最后一个元素都要大,说明不可以直接接到最后一个上面,且不会对前面的tails值产生影响。
- 如果nums的这个值,比tails当前最后一个元素小,那么我们就要去前面找,如果前面有和这个元素相等的,也不会产生影响,因为已经存在了,如果也没有相等的,就去tails数组中找到第一个比nums大的,并且替换它。
到这里,状态转移方程就算基本结束了。
二分的思想:
说了这么多,前面的思路大体都结束了,可是还没有用到二分查找的思想,这到底用到哪的呢?
当搜索空间有序的时候,就可以通过⼆分搜索「剪枝」,⼤幅提升效率。
二分这一思想就是针对有序的,常见的用在搜索上,可提升效率。
在这里,tails数组是严格升序的,而在状态转移方程中,我们需要不断的去tails里面搜索第一个比nums大的值,并换掉它,这个步骤,就可以使用二分法。
具体使用就是,用left、right双指针,不断的取中点进行比较,找到第一个比nums大的值后,替换它。
0x03.代码实现
class Solution {
public:
int lengthOfLIS(vector<int>& nums){
vector<int> tails(nums.size(),0);
int res=0;//tails的长度
for(int num:nums){
int left=0,right=res;
while(left<right){//二分搜索的过程
int mid=(left+right)/2;
if(tails[mid]<num) left=mid+1;
else right=mid;
}
tails[left]=num;//替换操作
if(right==res) res++;//如果是换到最右边,tails的长度加1
}
return res;
}
};
0x04.复杂度
时间复杂度为:O(N*logN)
空间复杂度为:O(N)
ATFWUS --Writing By 2020--03--16
来源:CSDN
作者:如颖随心->A
链接:https://blog.csdn.net/ATFWUS/article/details/104905620