单调栈和单调队列的应用即总结

单调栈

基本概念

单调栈的基础结构是“栈”,即元素是LIFO的,只能从栈顶控制数据出入;单调表示从栈的一端到另一端遍历,元素是单调递增或者单调递减的。比如单调递减栈,从栈底到栈顶的元素可以是:

bottom 10 7 5 3 2 1 top

单调栈获取和处理元素都是在线的方式。

假设有一个有序序列,元素是

1 6 3 4 2 3 4 5 8 9 7

我们想用一个单调递增的栈来遍历这个序列,则栈内元素变化是:

1
1 6
1 3
1 3 4
1 2
1 2 3
1 2 3 4
1 2 3 4 5 
1 2 3 4 5 8
1 2 3 4 5 8 9
1 2 3 4 5 7

单纯看上面的栈,可能没有意义。但是,设想一个这样的场景,我们需要在线获取满足下面条件的区间[l, r)
对于任意的m, l <= m <= r,都有arr[m] >= arr[l]
我们想要获取所有满足这种条件区间,反观上面的单调递减栈的进栈顺序,确实可以 满足这种条件,以前两个位例子:

1       # 一个元素必然是 
1, 6   # 1, 6 也满足这个区间
1, 3   # 1, 6, 3 这个序列也是,只不过6没有在栈中

为了记录区间范围,我们可以把栈存储元素,改成存储下标,这样可以动态的获取满足条件的区间。

应用

首先明确,单调栈适合处理依赖序列单调性质的问题,同时注意,单调栈在某种意义上是在线算法,因为是动态增删元素。

最经典的应用是求解直方图最大矩形的面积,题目分析在最后的参考文章中有,这里不再赘述。明确一点,从当前柱子向右扩展,那么扩展的最大边界是第一个比该柱子低的柱子。如果柱子是递增的,则每一个都可以作为新的启点。

#include <iostream>
#include <stack>
#include <vector>
#include <algorithm>

// 直方图最大矩形面积
int maxMartixArea(const std::vector<int>& line) {
    if (line.empty()) {
        return 0;
    }
    std::stack<int> st;
    int maxArea = -1;
    int N = line.size();
    for (int i = 0; i < line.size(); ++i) {
        while (!st.empty() && line[st.top()] >= line[i]) {
            int t = st.top();
            int s = (i - t) * line[t];
            maxArea = std::max(s, maxArea);  // 在线计算最大面积,处理连续递增的面积
            st.pop();
        }
        st.push(i);  // 每个元素入栈一次
    }
    // 处理栈中多余的元素
    if (!st.empty()) {  // 占位符
        N = st.top() + 1;
    }
    while (!st.empty()) {
        int t = st.top();
        int s = (N - t) * line[t];
        maxArea = std::max(s, maxArea);
    }
    return maxArea;
}

int main() {
    return 0;
}

单调队列

基本概念

基于单调栈的一个扩展,当单调栈的栈底元素可以弹出的时候,单调栈即可转化单调队列。单调队列是一个受限的双端队列,因为队头只能弹出元素。由单调栈可以知道,单调栈维护的是单调区间。以上面的单调递增队列为例子,栈底的元素,永远是当前区间中最小的值,因此单调队列可以在 O ( 1 ) O(1) O(1)的时间内,获取队首队尾区间的最值,因此单调的区间的最大用处,也在于获取区间的最值上。

一般来说,我们的区间需要满足某个递减条件,然后确定区间的长度,并求解区间的最值,队首元素会不断更替,以满足区间长度的需求。

应用

单调队列的最典型应用是求解滑动窗口最值。直接给出代码

std::vector<int> slideWinMaxNum(const std::vector<int>& arr, int winSize) {
    std::vector<int> res;
    if (arr.empty() || winSize <= 0 || arr.size() < winSize) {
        return res;
    }
    std::deque<int> deq;
    int N = arr.size();
    for (int i = 0; i < N; ++i) {
        while (!deq.empty() && arr[deq.back()] <= arr[i]) {
            deq.pop_back();
        }
        deq.push_back(i);
        if (i >= winSize - 1) {
            res.push_back(arr[deq.front()]);
        }
        if (i - deq.front() >= winSize) {
            deq.pop_front();
        }
    } 
}

最大值减去最小值大于等于num的区间个数:

#include <iostream>
#include <vector>
#include <deque>

/*
 *  基本思路是,利用两个单调队列,在以某个元素作为区间左边界的情况下,
 *  不断更替区间范围,并动态获取最值,之后利用最值之差和下标之差,计算
 *  符合条件的子数组的个数
 */

int MaxSubMinNum(const std::vector<int>& arr, int num) {
    if (arr.empty()) {
        return 0;
    }
    std::deque<int> qmax;  // 递减队列
    std::deque<int> qmin;  // 递增队列
    int res = 0;
    int i = 0;  // 区间左边界
    int j = 0;  // 区间右边界
    int N = arr.size();
    while (i < N) {
        while (j < N) {
            while (!qmax.empty() && arr[qmax.back()] <= arr[j]) {
                qmax.pop_back();  // 维护递减队列性质
            }
            qmax.push_back(j);
            while (!qmin.empty() && arr[qmin.back()] >= arr[j]) {
                qmin.pop_back();  // 维护递增队列性质 
            }
            qmin.push_back(j);
            if (qmax.front() - qmin.front() >= num) {
                break;  // 找到满足性质的区间,不再需要递增区间右边界范围
            }
            ++j;
        }
        if (qmax.front() == i) {
            qmax.pop_front();  // 以arr[i]为边界的区间查找结束
        }
        if (qmin.front() == i) {
            qmin.pop_front();
        }
        res += (j - i);  // arr[i]为边界的个数
        i++;  // 移动窗口右边界
    }
}

int main() {
    return 0;
}

参考文献

  • https://endlesslethe.com/monotone-queue-and-stack-tutorial.html
  • https://cloud.tencent.com/developer/article/1109268
  • https://blog.csdn.net/Dacc123/article/details/50545577
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在Python中,单调单调队列是两种不同的数据结构。单调是一个,它的特点是内的元素是单调的,可以是递增或递减的。在构建单调时,元素的插入和弹出都是在的一端进行的。与此类似,单调队列也是一个队列,它的特点是队列内的元素是单调的,可以是递增或递减的。在构建单调队列时,元素的插入是在队列的一端进行的,而弹出则是选择队列头进行的。 单调队列在解决某些问题时,能够提升效率。例如,滑动窗口最大值问题可以通过使用单调队列来解决。单调队列的结构可以通过以下代码来实现: ```python class MQueue: def __init__(self): self.queue = [] def push(self, value): while self.queue and self.queue[-1 < value: self.queue.pop(-1) self.queue.append(value) def pop(self): if self.queue: return self.queue.pop(0) ``` 上述代码定义了一个名为MQueue的类,它包含一个列表作为队列的存储结构。该类有两个方法,push和pop。push方法用于向队列中插入元素,它会删除队列尾部小于插入元素的所有元素,并将插入元素添加到队列尾部。pop方法用于弹出队列的头部元素。 总结来说,单调单调队列都是为了解决特定问题而设计的数据结构。单调在构建时元素的插入和弹出都是在的一端进行的,而单调队列则是在队列的一端进行的。在Python中,可以通过自定义类来实现单调队列的功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值