一.单调栈
算法原理:
用单调递增栈,当该元素可以入栈的时候,栈顶元素就是它左侧第一个比它小的元素
例题:
**用数组模拟栈实现的代码
#include <iostream>
using namespace std;
const int N = 100010; // 定义栈的最大大小
int stk[N], tt; // 定义栈数组和栈顶指针
int main()
{
int n; // n为输入元素个数
cin >> n; // 输入元素个数n
while (n--)
{
int x;
scanf("%d", &x); // 读取一个整数x
while (tt && stk[tt] >= x)
tt--; // 如果栈顶元素大于等于当前元素x,则栈顶元素出栈
if (!tt)
printf("-1 "); // 如果栈空,则输出-1,表示没有比当前元素小的值
else
printf("%d ", stk[tt]); // 输出栈顶元素,即当前元素左侧第一个比它小的元素
stk[++tt] = x; // 将当前元素入栈
}
return 0;
}
***用STL实现这道题的代码
#include<iostream>
#include<vector>
using namespace std;
int main() {
int n,x;
cin >> n;
vector<int> t;
while (n--) {
cin >> x;
while (t.size() > 0 and t.back() >= x) {
t.pop_back();
}
if (t.size() == 0) {
cout << -1 << ' ';
}else {
cout << t.back()<<' ';
}
t.push_back(x);
}
return 0;
}
二.单调队列
例题:滑动窗口
输入格式
输入包含两行。
第一行包含两个整数 nn 和 kk,分别代表数组长度和滑动窗口的长度。
第二行有 nn 个整数,代表数组的具体数值。
同行数据之间用空格隔开。
输出格式
输出包含两个。
第一行输出,从左至右,每个位置滑动窗口中的最小值。
第二行输出,从左至右,每个位置滑动窗口中的最大值。
输入样例:
8 3
1 3 -1 -3 5 3 6 7
输出样例:
-1 -3 -3 -3 3 3
3 3 5 5 6 7
三.滑动窗口的解析(借鉴来自acwing—海绵宝宝)
题目中提到了滑动窗口,我们先以示例为例看下什么是滑动窗口?在示例中,我们从数组中第一个元素开始遍历,由于窗口的大小是3,因此当遍历到第三个元素时,窗口就形成了。
之后,继续遍历元素时,为了保持窗口的大小为3,左侧元素就需要从窗口中剔除。这样使得窗口一直在向右移动,直到考察到最后一个元素结束,这就是所谓的滑动窗口。
算法原理:
解题思路(以最大值为例):
...由于我们需要求出的是滑动窗口的最大值。
...如果当前的滑动窗口中有两个下标 i 和 j ,其中i在j的左侧(i<j),并且i对应的元素不大于j对应的元素(nums[i]≤nums[j]),则:
...当滑动窗口向右移动时,只要 i 还在窗口中,那么 j 一定也还在窗口中。这是由于 i 在 j 的左侧所保证的。
因此,由于 nums[j] 的存在,nums[i] 一定不会是滑动窗口中的最大值了,我们可以将nums[i]永久地移除。
因此我们可以使用一个队列存储所有还没有被移除的下标。在队列中,这些下标按照从小到大的顺序被存储,并且它们在数组nums中对应的值是严格单调递减的。
当滑动窗口向右移动时,我们需要把一个新的元素放入队列中。
为了保持队列的性质,我们会不断地将新的元素与队尾的元素相比较,如果新元素大于等于队尾元素,那么队尾的元素就可以被永久地移除,我们将其弹出队列。我们需要不断地进行此项操作,直到队列为空或者新的元素小于队尾的元素。
由于队列中下标对应的元素是严格单调递减的,因此此时队首下标对应的元素就是滑动窗口中的最大值。
窗口向右移动的时候。因此我们还需要不断从队首弹出元素保证队列中的所有元素都是窗口中的,因此当队头元素在窗口的左边的时候,弹出队头。
#include <iostream>
#include <cstring>
#include <algorithm>
#include <deque>
using namespace std;
const int N = 1000010;
int a[N];
int main()
{
int n, k;
cin >> n >> k;
for (int i = 1; i <= n; i ++ ) cin >> a[i];//读入数据
deque<int> q;
for(int i = 1; i <= n; i++)
{
while(q.size() && q.back() > a[i]) //新进入窗口的值小于队尾元素,则队尾出队列
q.pop_back();
q.push_back(a[i]);//将新进入的元素入队
if(i - k >= 1 && q.front() == a[i - k])//若队头是否滑出了窗口,队头出队
q.pop_front();
if(i >= k)//当窗口形成,输出队头对应的值
cout << q.front() <<" ";
}
q.clear();
cout << endl;
//最大值亦然
for(int i = 1; i <= n; i++)
{
while(q.size() && q.back() < a[i]) q.pop_back();
q.push_back(a[i]);
if(i - k >= 1 && a[i - k] == q.front()) q.pop_front();
if(i >= k) cout << q.front() << " ";
}
}