单调栈与单调队列

单调栈与单调队列

单调栈

单调栈,顾名思义就是一种具备单调性质(内部元素满足单调性)的栈结构

现在来看一道例题:

向右看齐

\(n\) 个人从左到右站成一排,每个人都有一个身高,现要求所有人向右看齐,问每个人可以看到的最远的人是哪一个?(一个人右边第一个身高大于等于他的人会挡住这个人的视线 )。没有则输出 \(-1\)

数据范围:\(1\le n\le 10^6\)

简化题意:

给定含有 \(n\) 个数的连续序列 \(h\),对于每一个 \(i\) 找到一个最小的 \(j\in [i+1,n]\),使得 \(h[i]<h[j]\),若不存在则输出 \(-1\)

分析:

先考虑暴力,对于每一个位置 \(i\) 都向右寻找一个 \(j\),此时极限复杂度大约为 \(O(n^2)\)无法通过本题

为什么会超时呢?

假设序列 \(h = \{5,4,3,2,1\}\),从右到左计算,步骤如下:

  1. \(h_5\) 时直接得出答案 \(-1\)
  2. \(h_4\) 时查询 \(h_5\),得出答案 \(-1\)
  3. \(h_3\) 时查询 \(h_4,h_5\),得出答案 \(-1\)
  4. \(h_2\) 时查询 \(h_3,h_4,h_5\),得出答案 \(-1\)
  5. \(h_1\) 时查询 \(h_2,h_3,h_4,h_5\),得出答案 \(-1\)

可以发现每一个数都被查询了很多遍,这就是超时的原因

这时需要用一个栈来维护可能存在的解,维护步骤如下:

在从右到左扫描的过程中,若当前的值大于栈顶,则栈顶一定不优(永远也不会用到,因为有更优的解),出栈并放入新的解。

可以发现,这样维护的栈满足单调性,且栈顶元素是最大的。

时间复杂度见底部

代码:

代码使用STL栈,计算方式是从右至左

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6;
int n;
int a[maxn + 5];
stack<int> st;
int ans[maxn + 5];
int main() {
  scanf("%d",&n);
  for (int i = 1;i <= n;i ++) scanf("%d",&a[i]);
  for (int i = n;i >= 1;i --) {
    while (!st.empty() && a[st.top()] < a[i]) st.pop();
    ans[i] = st.empty() ? -1 : st.top();
    st.push(i);
  }
  for (int i = 1;i <= n;i ++) printf("%d\n",ans[i]);
  return 0;
}

单调队列

单调队列,本质上就是在单调栈的底部开一个口,用于排出非法解

经典例题:

滑动窗口

给定一个长度为 \(n\) 的数字序列 \(a\) 和一个正整数 \(k\),对于每一个 \(i\in[1,n-k+1]\) ,求区间 \([i,i+k-1]\) 的最大值与最小值

数据范围:\(1\le n \le 10^7\)

分析:

如果数据再小一点(\(n\le10^6\))就可以用ST表用通法解决。但显然,这道题并不是普通的区间最值查询,它固定了查询区间长度。也就是说在区间向右滑动时,一个元素有且可能被加入(删除)区间 \(1\) 次,这满足上面对单调栈”永远不会用到的元素“的分析。

与单调栈思路相同,只是增加了一个条件,若栈底(队头)元素的坐标小于给定区间的开头,出队

代码:

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e7;
int n,k;
int a[maxn + 5];
deque<int> qmax,qmin;
int mx[maxn + 5],mn[maxn + 5];
int main() {
  scanf("%d %d",&n,&k);
  for (int i = 1;i <= n;i ++) scanf("%d",&a[i]);
  for (int i = n;i >= 1;i --) {
    while (!qmax.empty() && a[qmax.back()] < a[i]) qmax.pop_back(); // 删除不优解
    while (!qmax.empty() && qmax.front() > i + k - 1) qmax.pop_front(); // 排除在边界外的答案
    qmax.push_back(i);
    if (i <= n - k + 1) mx[i] = a[qmax.front()];

    while (!qmin.empty() && a[qmin.back()] > a[i]) qmin.pop_back();
    while (!qmin.empty() && qmin.front() > i + k - 1) qmin.pop_front();
    qmin.push_back(i);
    if (i <= n - k + 1) mn[i] = a[qmin.front()];
  }
  for (int i = 1;i <= n - k + 1;i ++) printf("%d ",mn[i]);
  printf("\n");
  for (int i = 1;i <= n - k + 1;i ++) printf("%d ",mx[i]);
  printf("\n");
  return 0;
}

时间复杂度

每个元素仅入栈出栈(出队入队) \(1\) 次,时间复杂度为\(O(n)\)

posted @ 2025-08-25 23:33  nightmare_lhh  阅读(6)  评论(0)    收藏  举报