目录
什么是单调栈?
单调栈,顾名思义,就是具有单调性的栈。它依旧是⼀个栈结构,只不过⾥⾯存储的数据是递增或者递减的(这里假设是严格递增或者递减的)。这种结构是很容易实现的(如下⾯的代码),但重点是维护⼀个单调栈的意义是什么?
cpp
#include <iostream>
#include <stack>
using namespace std;
const int N = 3e6 + 10;
int a[N], n;
void test1()
{
stack<int> st; // 维护⼀个单调递增的栈
for (int i = 1; i <= n; i++)
{
// 栈⾥⾯⼤于等于 a[i] 的元素全部出栈
while (st.size() && st.top() >= a[i]) st.pop();
st.push(a[i]);
}
}
void test2()
{
stack<int> st; // 维护⼀个单调递减的栈
for (int i = 1; i <= n; i++)
{
// 栈⾥⾯⼩于等于 a[i] 的元素全部出栈
while (st.size() && st.top() <= a[i]) st.pop();
st.push(a[i]);
}
}
单调栈解决的问题
单调栈能帮助我们解决以下四个问题:
• 寻找当前元素左侧,离它最近,并且⽐它⼤的元素在哪;
• 寻找当前元素左侧,离它最近,并且⽐它⼩的元素在哪;
• 寻找当前元素右侧,离它最近,并且⽐它⼤的元素在哪;
• 寻找当前元素右侧,离它最近,并且⽐它⼩的元素在哪。
虽然是四个问题,但是原理是⼀致的。因此,只要解决⼀个,举⼀反三就可以解决剩下的⼏个。
寻找当前元素左侧,离它最近,并且⽐它⼤的元素在哪
从左往右遍历元素,构造⼀个单调递减的栈。插⼊当前位置的元素的时:
• 如果栈为空,则左侧不存在⽐当前元素⼤的元素;
• 如果栈⾮空,插⼊当前位置元素时的栈顶元素就是所找的元素。
注意,因为我们要找的是最终结果的位置。因此,栈⾥⾯存的是每个元素的下标
模板:
cpp#include <iostream> #include <stack> using namespace std; int n; const int N = 1e5 + 10; int arr[N],ret[N]; void test() { stack<int> st; for (int i = 1; i <= n; i++) { while (st.size() && arr[st.top()] <= arr[i]) st.pop(); if (st.size()) ret[i] = st.top(); st.push(i); //存储的是元素的下标!!! } for (int i = 1; i <= n; i++) cout << ret[i] << " "; cout << endl; } int main() { cin >> n; for (int i = 1; i <= n; i++) cin >> arr[i]; test(); return 0; }
利用上面的模板举⼀反三就可以解决剩下的⼏个,无非是遍历的顺序、单调递增还是单调递减:
找左侧,正遍历;找右侧,逆遍历;
⽐它⼤,单调减;⽐它⼩,单调增。
时间复杂度:每个元素最多出栈和入栈一次,所以时间复杂度是O(N)
什么是单调队列?
单调队列,顾名思义,就是具有单调性的双端队列。它依旧是⼀个队列结构,只不过⾥⾯存储的数据是递增或者递减的(这里假设是严格递增或者递减的)。这种结构是很容易实现的(如下⾯的代码),但重点是维护⼀个单调队列的意义是什么?
单调队列解决的问题
单调队列能帮助我们解决快速求滑动窗口内的最大值和最小值

cpp
#include <iostream>
#include <deque>
using namespace std;
const int N = 1e6 + 10;
int arr[N], n, k;
int main()
{
cin >> n >> k;
for (int i = 1; i <= n; i++) cin >> arr[i];
deque<int> q;
//求窗口内最小值 - 单调递增队列
for (int i = 1; i <= n; i++)
{
while (q.size() && arr[q.back()] >= arr[i]) q.pop_back();
q.push_back(i); // 存储的是元素的下标!!!
// 在输出结果之前,判断窗口是否合法
//if (q.size() > k) q.pop_front(); // error
if(q.back() - q.front() + 1 > k) q.pop_front();
if (i >= k) cout << arr[q.front()] << ' ';
}
cout << endl;
q.clear();
//求窗口内最大值 - 单调递减队列
for (int i = 1; i <= n; i++)
{
while (q.size() && arr[q.back()] <= arr[i]) q.pop_back();
q.push_back(i); // 存储的是元素的下标!!!
// 在输出结果之前,判断窗口是否合法
//if (q.size() > k) q.pop_front();// error
if (q.back() - q.front() + 1 > k) q.pop_front();
if (i >= k) cout << arr[q.front()] << ' ';
}
return 0;
}
注意:判断窗口是否合法的方法必须是 q.back() - q.front() + 1 > k,而不是单纯的 q.size() <= k.