笔者最近在刷LeetCode习题,故想将一些经典算法以及一些经典的数据结构汇编成一个系列,分享给大家和以后的自己。本文主要涉及单调栈

单调栈

单调栈本身的概念并不难理解,但是实际应用起来需要理解深刻才行。笔者当初理解了一个晚上才弄明白,而你们只需要看完这半篇文章即可完全理解。

关于递增栈和递减栈很多地方说法不一,此处为了方便说明,将栈底到栈顶单调增的即为递增栈;单调减的即为递减栈。(不一定正确,但是不影响之后的理解)。

先讨论递增栈,此处我定义一个概念:极左邻元素和极右邻元素。假设数组nums,其中当前元素的下标为k,那么若满足nums[q] < nums[k],且在区间[q+1,k-1]中的元素全都大于nums[k]。那么nums[q]即为nums[k]的极左邻元素,同理可以定义极右邻元素。

递减栈中类似,此处不再赘述。

光看上面的定义确实很好理解,让我们来思考一下单调栈的作用。它能够帮助我们找到当前元素的极左邻元素和极右邻元素,从而实现一些功能。搬一道经典题目,

链接:https://leetcode-cn.com/problems/largest-rectangle-in-histogram/

给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。求在该柱状图中,能够勾勒出来的矩形的最大面积。

image

图中阴影部分为所能勾勒出的最大矩形面积,其面积为 10 个单位。

此题若采用暴力搜索的方式固然很复杂,其实用单调栈即可在O(n)的时间内解决。大家先理解一个“扩散”的概念,假设我现在选择倒数第二个柱子(值为2),要想以此为中心,勾勒出最大的矩形,那么从这个柱子开始,左右扩散。左边的柱子比自己高,扩散成功,右边的柱子也比自己高,扩散成功。所以最终可以扩散到[5,6,2,3]这几根柱子上,那么以当前这个柱子为高,所能勾勒最大矩形面积即为2*4 = 8。那么这种“扩散”是在哪里停止的呢?显然,是在比当前元素小的且离当前元素最近的元素停止,这不就是上文中提到的极左邻元素嘛!所以只要能确定每个元素的极左邻元素和极右邻元素,那么直接一减就是宽度了呀。

如何利用单调栈找极左邻、极右邻元素?

维护一个递增栈。注意:栈中放的是元素的下标。要获取元素比大小的话,直接通过下标去数组中找就好了。

image

单调栈的代码模板

int stack_F(vector<int>& nums){
    int maxx;
    stack<int>st;
    //此处的-1是比nums中所有数都要小的数,也可以-1000,自己定。主要是为了让栈中的所有元素都可以被弹出,因为要维护单调栈,遇到一个很小的数,栈中的元素肯定全都会被弹出来。有利于将所有解都遍历到。
    nums.push_back(-1);
    for(int i=0;i<nums.size();i++){
        //此处等号可取可不取
        if(st.empty() || nums[st.top()] <= nums[i]){
            //存的是下标哦!
            st.push(i);
        }
        else{
            while(!st.empty() && nums[st.top()] > nums[i]){
                //注意你是站在栈顶元素的立场来看的喔!
                int index = st.top();st.pop();
                int left_boder = st.top();
                int right_boder = i;
                //有了这些边界点,就可以自定义统计左右符合条件的数量啦!比较maxx的值等。
                //your code ...
            }
            st.push(i);
        }
    }
    return maxx;
}

至此单调栈就结束了,下次更新的主题《递归的好基友,动态规划?啥是无后效性?》,欢迎点赞收藏!


Alec
8 声望4 粉丝