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

目录

什么是单调栈?

单调栈解决的问题

什么是单调队列?

单调队列解决的问题


什么是单调栈?

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

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.

相关推荐
乐迪信息22 分钟前
智慧港口船舶AI算法实现在线状态监测
大数据·人工智能·深度学习·算法·计算机视觉
kiracrimson1 小时前
从缓存的角度看链表与线性表的差异
数据结构·链表·缓存
木井巳2 小时前
【BFS/DFS 解决 FloodFill 算法】太平洋大西洋水流问题
java·算法·leetcode·深度优先·广度优先·宽度优先·推荐算法
心抵鹊2 小时前
归并排序之翻转对(hard)
数据结构·算法
白山编程大哥3 小时前
Java 集合算法:从排序、查找到底层原理的实战指南
java·python·算法
shehuiyuelaiyuehao3 小时前
算法34,位运算符操作,总结
算法
Navigator_Z3 小时前
LeetCode //C - 1224. Maximum Equal Frequency
c语言·算法·leetcode
Navigator_Z4 小时前
LeetCode //C++ - 1226. The Dining Philosophers
c语言·算法·leetcode
FOORIR4 小时前
3D视觉+AI客流系统怎么做:从Sensor Pipeline到数据事件引擎
算法
Omics Pro6 小时前
Arc研究所Cell|全新虚拟细胞官方基准评测框架
大数据·人工智能·深度学习·算法·机器学习