【算法】单调栈和单调队列

目录

什么是单调栈?

单调栈解决的问题

什么是单调队列?

单调队列解决的问题


什么是单调栈?

单调栈,顾名思义,就是具有单调性的栈。它依旧是⼀个栈结构,只不过⾥⾯存储的数据是递增或者递减的(这里假设是严格递增或者递减的)。这种结构是很容易实现的(如下⾯的代码),但重点是维护⼀个单调栈的意义是什么?

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)

什么是单调队列?

单调队列,顾名思义,就是具有单调性的双端队列。它依旧是⼀个队列结构,只不过⾥⾯存储的数据是递增或者递减的(这里假设是严格递增或者递减的)。这种结构是很容易实现的(如下⾯的代码),但重点是维护⼀个单调队列的意义是什么?

单调队列解决的问题

单调队列能帮助我们解决快速求滑动窗口内的最大值和最小值

模板题:P5788 【模板】单调栈 - 洛谷

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.

相关推荐
岛雨QA2 小时前
图「Java数据结构与算法学习笔记12」
数据结构·算法
czxyvX2 小时前
020-C++之unordered容器
数据结构·c++
岛雨QA2 小时前
多路查找树「Java数据结构与算法学习笔记11」
数据结构·算法
AKA__Zas2 小时前
初识基本排序
java·数据结构·学习方法·排序
_Li.2 小时前
Simulink - 6DOF (Euler Angles)
人工智能·算法·机器学习·游戏引擎·cocos2d
岛雨QA2 小时前
树结构实际应用「Java数据结构与算法学习笔记10」
数据结构·算法
zephyr052 小时前
DP 从放弃到拿捏:一份持续更新的动态规划题解清单(一)
算法·动态规划
岛雨QA2 小时前
树结构的基础部分「Java数据结构与算法学习笔记9」
数据结构·算法
会编程的土豆2 小时前
2.25 做题
数据结构·c++·算法