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

目录

什么是单调栈?

单调栈解决的问题

什么是单调队列?

单调队列解决的问题


什么是单调栈?

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

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.

相关推荐
weisian1518 分钟前
Java并发编程--47-分布式ID生成器:雪花算法(Snowflake)与时钟回拨问题
java·算法·时钟回拨·雪花算法id
itzixiao9 分钟前
L1-066 猫是液体(5分)[java][python]
java·开发语言·python·算法
ytttr87311 分钟前
MATLAB SIFT图像配准实现
算法·机器学习·matlab
小饕14 分钟前
从 Word2Vec 到多模态:词嵌入技术的演进全景
人工智能·算法·机器学习
海参崴-15 分钟前
AVL树完整实现与深度解析
算法
一个爱编程的人23 分钟前
一个数是不是素数
数据结构·算法
Hui_AI72027 分钟前
基于RAG的农产品GEO溯源智能问答系统实现
开发语言·网络·人工智能·python·算法·创业创新
lwf00616428 分钟前
FFM (Field-aware Factorization Machine) 学习日记
算法·机器学习
南宫萧幕28 分钟前
HEV能量管理控制算法实战:从MPC/RL理论基础到Simulink闭环建模
算法·matlab·汽车·控制·pid
IT猿手35 分钟前
SCI一区:章鱼优化算法(Octopus Optimization Algorithm, OOA)求解23个测试函数,出图丰富,提供完整MATLAB代码
开发语言·算法·matlab