【刷题总结】 单调栈和单调队列

单调栈

作用:查找每个数的左侧第一个比它小或者比它大的数。

单调递减栈:查找数的左侧第一个比它大的数

单调递增栈:查找数的右侧第一个比它小的数

84.柱状图中最大矩形 hard

给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。

求在该柱状图中,能够勾勒出来的矩形的最大面积。

                                 

分析:

枚举每个矩形,作为它的上边界。找出左边离它最近的比它小的柱形,找出右边离它最近的比它小的柱形,作为它的左右边界。

暴力解法为,对于每个矩形都向左向右遍历一遍,此时时间复杂度为O(n^2)

此时,可以用可以用单调递增栈进行优化,栈内的元素单调递增,当遇到第i个元素,小于栈顶元素时,此时栈顶元素的左边界就是栈内的第二个元素,栈顶元素的右边界就是第i个元素,计算以栈顶元素作为上边界的面积即可。

代码:

int LargestRectangleArea(vector<int>& heights)
{
    int res = 0;
    stack<int> s;
    
    heights.push_back(0); //加入右边界
    for(int i = 0; i < height.size(); i++) 
    {
        if(s.empty() || height[i] >= height[s.top()])
            s.push(i);
        else 
        {
            int h = height[s.top()]; s.pop();
            // int area = h * (i - s.top() - 1); 不要忘记考虑栈为空的情况
            int area = h * (s.empty() ? i : i - s.top() - 1);
            res = max(res, area);
            i--;
       }
    }
}

42.接雨水 hard

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。 感谢 Marcos 贡献此图。

分析:此题采用的是单调递减栈,寻找该数左侧第一个大于它的数。然后计算所能接到的雨水数。

代码:

int trap(vector<int>& height) 
{
    if(height.size() < 2) return 0;
    int area = 0, hs = height.size();
    stack<int> q;
    for(int i = 0; i < hs; i++) 
    {
        if(q.empty() || height[q.top] > height[i]) q.push(i);
        else 
        {
            int t = 0;
            while(!q.empty() && height[q.top()] <= height[i]) 
            {
                area += (height[q.top()]  - t) * (i - q.top() - 1);
            }
            if(!q.empty()) area = (height[i] - t) * (i - q.top() - 1);
            q.push(i);
        }
    }
    return area;
}

单调队列

单调队列我见过的使用最经典的就是滑动窗口最大值的问题.

239.滑动窗口最大值

给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。

返回滑动窗口中的最大值。

分析:

暴力解法:遍历数组中的所有值,查找其对应滑动窗口中的最大值。时间复杂度是O(N*K)。

进阶:如果希望采用线性时间复杂度,则使用单调队列记录滑动窗口当前的最大值。

维护一个单调递减队列,如果前一个数小于后一个数,则前一个数不可能是该滑动窗口的最大值,可直接删掉。

deque<int> q;
//目标:维护一个单调递减队列,其中队列一直是一个滑动窗口的长度

for(int i = 0; i < nums.size(); i++) 
{
    //控制滑动窗口的大小
    if(q.size() && i - k + 1 > q.front()) q.pop_front();
    //维护这个单调递减队列
    while(q.size() && nums[q.back()] < nums[i]) q.pop_back();
    q.push_back(i);
    //记录滑动窗口的最大值
    if(i >= k - 1) res.push_back(nums[q.front()]);
}

总结:

思路都很巧妙,但是我觉得还挺难的。面试中似乎很难考到(我是没有过)

主要有一些容易忘记的点:

1. 存储的是序号,目的是为了容易计算出横坐标。

2. 注意边界情况,比如栈,队列为空,或者遍历到了最后的情况。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值