单调栈与单调队列

Posted qbits

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了单调栈与单调队列相关的知识,希望对你有一定的参考价值。

单调栈

特点

栈内的元素单调递增或者单调递减,可以在\(O(n)\)的时间内求出数列中所有数的左边或右边第一个比其大或小的元素,总时间复杂度为\(O(n)\)

例子

单调栈中一般存索引
一个单调递增栈s = [0, 10, 20 ,t]代表栈中a[1]~a[9]的元素大于a[10]的元素,索引为a[11]~a[19]的元素大于a[20]的元素...
这样我们可以发现在a[10]左边第一个比a[10]的数为a[0],在a[20]左边第一个比a[20]的数为a[10]...
如何实现呢?
每次有元素a[i] = x进栈时,若栈顶的元素a[t] >= x,出栈,否则进栈,当然栈为空时直接进栈
栈顶元素出栈时,由于栈顶比要入栈的元素大,所以a[t]右边第一个比a[t]小的值为x

这样我们就能够求一个数x左边和右边第一个比x小的数

例题84. Largest Rectangle in Histogram

class Solution {
public:
    int largestRectangleArea(vector<int>& heights) {
        stack<int> s;
        int area = 0;
        heights.push_back(0);
        for(int i = 0; i < heights.size(); i++){
            while(!s.empty() && heights[s.top()] >= heights[i]) {
                int j = s.top();
                s.pop();
                area = max(area, heights[j] * (s.size() == 0 ? i: (i - (s.top() + 1)) ));
            }
            
            s.push(i);
        }
        
        return area;
    }
};

85. Maximal Rectangle
与上题类似,需要处理一下

单调队列

特点

队列中元素单调,队首,队尾可以出队,队尾可以入队
区间最值问题可以使用ST表和线段树来解决,但经常用于多次查询,时间复杂度为\(O(n\lg(n))\)
如果是类似于滑动窗口的区间最值,则可以用单调队列来解决,时间复杂度为\(O(n)\)
例题239. Sliding Window Maximum
这是个单调递减队列,队列维护滑动窗口,队列中位于前面且比后面小的一定不会是最大值,所以入队时可以踢出
理解了单调栈,单调队列也比较好理解
单调队列其他题目

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        deque<int> q;
        vector<int> v;
        for(int i = 0; i < k - 1; i++){
            while( !q.empty() && nums[q.back()] <= nums[i]) q.pop_back();
            
            q.push_back(i);
        }
        
        for(int i = k-1; i < nums.size(); i++){ 
            while( !q.empty() && nums[q.back()] <= nums[i]) q.pop_back();
            q.push_back(i);
            while( !q.empty() &&  i - q.front() >= k) q.pop_front();
            v.push_back(nums[q.front()]);
        }
        
        return v;
    }
};

862. Shortest Subarray with Sum at Least K

class Solution {
public:
    int shortestSubarray(vector<int>& A, int K) {
        int sum = 0;
        deque<int> q;
        int l = A.size() + 1;

        vector<int> B(l, 0);
        B[0] = 0;
        for(int i = 0; i < A.size(); i++) B[i+1] = B[i] + A[i];
        for(int i = 0; i < B.size(); i++){
          
           
            
            
            while(!q.empty() && B[i] <= B[q.back()]) q.pop_back();
            q.push_back(i);
            while(!q.empty() && B[i] - B[q.front()] >= K) {
                l = min(l, i - q.front());
                q.pop_front();
            }
            
        }
        
        return l == A.size() + 1 ? -1 : l;
    }
};  

使用单调队列的基本步骤大致为

1.满足单调性

当遍历到a[i]时,需要将a[i]入栈,首先从队列弹出在未来的遍历中一定比a[i]劣的值
在滑动窗口中的
while( !q.empty() && nums[q.back()] <= nums[i]) q.pop_back();
表示在未来的滑动窗口中一定不会使用比之前小于等于nums[i]的值

与862题中的
while(!q.empty() && B[i] <= B[q.back()]) q.pop_back();
在未来的前缀和中如果需要B[i]为首,那么B[i]越小越好,在B[i]之前且小于等于B[i]的前缀和比B[i]劣

2.i入队列

3.满足具体约束

在滑动窗口中
具体的约束为区间长度
while( !q.empty() &&  i - q.front() >= k) q.pop_front();

在862题中表示为
while(!q.empty() && B[i] - B[q.front()] >= K) {
            l = min(l, i - q.front());
            q.pop_front();
        }

以上是关于单调栈与单调队列的主要内容,如果未能解决你的问题,请参考以下文章

单调队列

poj-2823 单调队列

单调队列单调栈优先队列模板

「CSPS 2019 十一」数据结构

单调队列

单调队列 单调栈