继续探索-最长上升子序列--贪心+二分+动态规划(综合运用)

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 

发布了126 篇原创文章 · 获赞 142 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/ATFWUS/article/details/104905620