滑动窗口___2023-08-31
滑动窗口
目录
概念定义
窗口 简单理解是一段[L, R]
区间, 但其左右边界会移动, 满足以下原则:
- 左右边界不能往左移动
- 左边界始终不超过右边界
显然, R
右移可以看做一个元素进窗口, L
右移可以看做一个元素出窗口
研究窗口的目的是要设计一种算法, 使得用户每次改变新的窗口状态后, 能用较小的代价反馈当前窗口的某个信息(一般是最值)
流程
我们假定要求得到最大值
准备一个双端队列, 其中储存元素的下标
我们要求严格保证该队列中头部到尾部下标对应的值从大到小依次排列
L
和R
初始在-1
位置
R
移动流程
- 看新进来的数能否直接添加至队列的尾部
- 如果要进来的元素小于对位队尾(或者队伍空着), 直接加入队尾
- 如果不能, 一直弹出队尾, 直到能进来为止
这里弹出来的元素不用跟踪了
因为相较于后续进来的值, 前面的数会较后面的先 “过期” , 也就是被L
踢出去
因此再也不可能成为窗口的最值了, 无需跟踪
如果两个大小一样留谁?
根据上面的分析不难得出, 要留下标靠后的
因为两者大小一样, 但后面的晚过期
L
移动流程
- 看一眼队列头部的下标和当前
L
来到的下标是否一致 - 如果一致, 那么头部弹出
- 如果不一致, 无事发生
双端队列的实质: 元素依次过期, 能成为最大值的那些可能
复杂度分析
只看滑动一次的话, 可能会出发一直弹出的情况
但是如果滑过$n$个数的话, 所有数字进队列一次, 出队列一次, 维护的代价是$O(n)$, 因此平均每次维护是$O(1)$
代码
#include <bits/stdc++.h>
using namespace std;
class GlideWindow
{
public:
int L, R, len;
deque<int> q;
vector<int> arr;
void setUp(vector<int> a)
{
L = R = -1;
len = a.size();
arr = a;
}
int inquire()
{
if (q.empty())
return NULL;
return arr[q.front()];
}
void moveL()
{
if (L == R)
{
cout << "L and R is overlaped, can't move L." << endl;
return;
}
if (q.front() == ++L)
q.pop_front();
}
void moveR()
{
if (++R == len)
{
cout << "R is on the right boarder of the array, can't move." << endl;
return;
}
while (!q.empty() && arr[q.back()] <= arr[R])
{
q.pop_back();
}
q.push_back(R);
}
};
int main()
{
vector<int> arr = {1, 4, 2, 8, 5, 7};
GlideWindow gw;
gw.setUp(arr);
int LorR = 0;
while (true)
{
cout << "Move L or R? [1 / 2, 0 to exit]: ";
cin >> LorR;
if (!LorR)
break;
if (LorR == 1)
gw.moveL();
else
gw.moveR();
cout << "arr: ";
for (auto i : arr)
cout << " " << i;
cout << endl;
cout << " ";
for (int i = 0; i < arr.size(); i++)
{
cout << ((gw.L == i - 1) ? "L" : " ");
cout << ((gw.R == i - 1) ? "R" : " ");
}
cout << endl;
cout << "Max of the window: " << gw.inquire() << endl;
for (int i = 0; i < 20; i++)
{
cout << "- ";
}
}
}
这里我们人为规定
L
和R
是左开右闭的
即, L移动到的位置过期, R移动到的位置加入
为了便于理解还忍不住做了可视化输出(乐)