数据结构-优化单调栈求最长上升子序列

单调栈优化求最大连续上升子序列。

先说说什么是单调栈,此部分内容转自 https://blog.csdn.net/lucky52529/article/details/89155694

1 单调栈

有两种:

  • 单调递增栈:数据出栈的序列为单调递增序列,也就是从栈底到栈顶的数据从大到小。
  • 单调递减栈:数据出栈的序列为单调递减序列,也就是从栈底到栈顶的数据从小到大。

模拟单调栈的数据push和pop
模拟实现一个递增单调栈:

现在有一组数10,3,7,4,12。从左到右依次入栈,则如果栈为空或入栈元素值小于栈顶元素值,则入栈;否则,如果入栈则会破坏栈的单调性,则需要把比入栈元素小的元素全部出栈。单调递减的栈反之。

  • 10入栈时,栈为空,直接入栈,栈内元素为10。
  • 3入栈时,栈顶元素10比3大,则入栈,栈内元素为10,3。
  • 7入栈时,栈顶元素3比7小,则栈顶元素出栈,此时栈顶元素为10,比7大,则7入栈,栈内元素为10,7。
  • 4入栈时,栈顶元素7比4大,则入栈,栈内元素为10,7,4。
  • 12入栈时,栈顶元素4比12小,4出栈,此时栈顶元素为7,仍比12小,栈顶元素7继续出栈,此时栈顶元素为10,仍比12小,10出栈,此时栈为空,12入栈,栈内元素为12。

至于出现相等的元素根据题目要求进行取舍。
好,切入正题。

2 单调栈优化

对于前一个举例的单调栈,如果新加入的元素大于栈顶元素,我们是从栈顶for到栈底,直到找到第一个小于它的元素,那么要增加一个元素时间复杂度最坏是O(n)。所以要用上upper_bound或lower_bound函数去查找第一个小于它的元素(用数组模拟栈),然后直接用要插入的元素替换即可,当然,不用弹出之前的栈顶元素,它们还有作用。这样每次增加一个元素时间复杂度最坏为O(logn)。
这样优化就完成了,当然,为了解决问题。我们要研究的是单调栈优化后所具有的一些新的性质。接下来先给出方法,然后用数学归纳法(递推)去证明:

先梳理一遍:对于求一个序列的(栈内不允许重复元素),我们可以维护一个优化单调栈,将原序列的元素从头开始通过某种方法不断压入,使得栈内每个元素的下标为以该元素为结尾的连续上身子序列的最长长度(这个是新的性质,也是要证明的内容)。把原序列最后一个元素压入时,栈顶元素的下标就是就是最长长度。

步骤:

  • 栈为空,压入原序列首个元素(规定数组以下标1为开头)
  • 栈非空时,压入第i个元素:
    1 如果第i个元素大于栈顶,直接把第i个元素压入
    2 如果第i个元素等于栈顶,替换为栈顶元素
    3 如果第i个元素小于栈顶,用upper_bound找到栈内第一个大于等于它的元素并替换
  • 不断压入元素直到把原序列所有元素压入。栈顶元素下标即为长度。

证明:
假设,压入第i-1个元素后,栈内元素的下标表示以该元素为结尾的最长上升序列长度,且栈内元素保持升序(为了方便理解,下面都默认从栈底到栈顶)。
那么,压入第i个元素时:

  • 如果大于栈顶,直接压入,首先栈内元素保持升序,而这个元素可以理解为接在了以栈顶元素为最后一个元素的最长上升序列后面,所以此时它的下标就是长度;且由于此时栈内没有下标更大的了,所以压入后的元素下标就是以该元素为结尾的最长上升序列长度。
  • 如果等于,同理,这个元素可以理解为替换了以栈顶元素为最后一个元素的最长上升序列的最后一个元素。
  • 如果小于,直接替换掉栈内大于等于它的第一个元素k。想想此时栈内仍会保持升序。这个元素可以理解为替换了以k为最后一个元素的最长上升序列的最后一个元素。它的下标就是长度不解释。问题就是证明为何是最长。也就是为什么不能替换在以 | k之后的任意一个元素 a 为结尾 | 的最长上升子序列最后一个元素。以a为结尾的连续上升子序列,其倒数第二个元素肯定是比将要加入的元素大的,所以不能。
    综上,压入第i个元素后的栈也满足假设。

最后一点我解释得很马虎,但也不难想,有个大前提就是每个元素都是顺序加入的,联系起来分类讨论一段时间就可以想通了。

好了,由于压入的第一个元素的栈满足假设,那么之后所有都满足。

以下是奇丑无比的代码

3 代码

    len=1;
    sta[len]=h[1];//h为原序列,sta数组模拟栈
    for(int i=2;i<=cnt;++i)
    {
        if(h[i]>sta[len]) sta[++len]=h[i];
        else if(h[i]==sta[len]) sta[len]=h[i];//可以和第三种情况合并
        else
        {
            q=lower_bound(sta,sta+len,h[i]);
            *q=h[i];
        }
    }
    printf("\n%d",len);
    return 0;
}
}

4 最后

同样的思路,譬如最长连续下降子序列啊,最长不下降子序列啊,最长连续不上升子序列啊,都同理可写。
顺带一提,不上升和不下降那个用的是upper_bound。
如果有错误欢迎批评。

posted @ 2021-02-09 21:21  七铭的魔法师  阅读(411)  评论(0编辑  收藏  举报