滑动窗口
/**
* 暴力做法
*/
#include <iostream>
#include <limits.h>
using namespace std;
const int N = 1e6 + 10;
int n, k;
int a[N];
int vec_max[N], vec_min[N], tt_max, tt_min;
int main()
{
cin >> n >> k;
for (int i = 0; i < n; ++ i) cin >> a[i];
for (int i = 0; i <= n - k; ++ i)
{
int maxx = INT_MIN, minn = INT_MAX;
for (int j = i; j < i + k; ++ j)
{
maxx = max(maxx, a[j]);
minn = min(minn, a[j]);
}
vec_max[tt_max ++] = maxx;
vec_min[tt_min ++] = minn;
}
for (int i = 0; i < tt_min; ++ i) cout << vec_min[i] << ' ';
cout << endl;
for (int i = 0; i < tt_max; ++ i) cout << vec_max[i] << ' ';
return 0;
}
/**
* 正解
* 步骤是先考虑暴力的做法,然后找到没有用的元素,比如在单调栈”距离最近的较小值“中无用的元素就是相较于当前值前面的较大值,因为它们一定更不会作为后面数据的答案,所以去掉即可
* 本题的滑动的窗口的实现形式和队列非常相似,窗口的每次滑动都是队头弹出一个元素,队尾添加一个元素
* 考虑在找最小值时,1 3 -1 -3 4,当3 -1 -3处于同一个窗口时,3和-1对于后面新添加的数据来说一定不会是选择的最小值,因为有-3的存在,选定的最小值就一定不会是它们,所以当-3出现后,对于后面的元素,完全没有必要再去考虑3和-1,他们也就是无用元素
* 考虑最大值时,-3 -1 3当3出现后,对于后面的数据,因为3的存在,-3和-1显然都不会是答案,所以它们也就成为了无用元素
* 这道题对我来说的难点在于很难想到分别去求最小值和最大值,因为我想到的就是同时求,如果没有想到分别求,那就很难想到这样的优化了,说起来不算困难,但是代码实现初步感觉有点混乱,不知道怎么去实现
*
* 以求最小值为例,每个数据都会进入队列,在数据m进入后,队列中已有元素中>=m的元素就一定不会是答案了,所以要先把这些数从队列中去掉,这也就完成了上面说到的去除重复元素的任务,之后再把m放进队列即可
* 大体的思路是这样的,但是很多细节还需要注意
* 队列要存放下标而非数值,原因是队头一定是窗口内最小元素(因为每次我们都把更大的元素弹出去了),如果能取到这个元素的前提是这个元素还在窗口内部,也就是我们需要能够定位元素位置,如果队列存储数组我们是无法定位的,所以存储下标
*
* 程序写完之后,感觉到正解就是用单调队列把暴力中找最小值的过程从o(N)降到了o(1)
* 队列维护的在原数组的窗口内要么是当前的最小值,要么可能是之后的最小值的数,原数组窗口中那些保证不会是答案的数全部都被删除了
* 而且不好理解的就是窗口向后移动时,离开窗口的数据未必是队列中的,它可能属于那些保证不会是答案的数
*/
#include <iostream>
using namespace std;
const int N = 1e6 + 10;
int n, k, a[N];
int hh, tt, q[N];
int main()
{
cin >> n >> k;
for (int i = 0; i < n; ++ i) cin >>a[i];
// 求窗口内最小值
hh = 0, tt = -1; // 数组模拟队列时提到的初始化方式,不一定非要这么写,把细节处理好即可
for (int i = 0; i < n; ++ i)
{
// 窗口向后滑动一位,有原有数组中新的数据进入窗口
while (hh <= tt && a[q[tt]] >= a[i]) -- tt; // 把前面更大的数据弹出队列,它们在后续的滑动过程中一定不会作为答案,hh <= tt的必要性在于需要保证q[tt]下标的合法性
q[++ tt] = i;
// 窗口向后滑动一位,有原有数组中一些数据要离开窗口
// 此时有两种情况,离开窗口的是我们已经排除的不可能是答案的数据,也可能是当前的最小值,只有后者才是在队列中的,所以我们要做的就是判断队头元素是不是要离开窗口了,此时队列存储下标再一次派上了用场
if (hh <= tt && q[hh] < i - k + 1) ++ hh; // a1 a2 a3 a4,如果k是3,当i到3(a4)时,那么窗口的左端点下标应该为1(a2),即3 - 3 + 1(i - k + 1),删除头节点的前提是队列不为空,即 hh <= tt
if (i >= k - 1) cout << a[q[hh]] << ' '; // 从满足长度为3的节点开始输出答案
}
cout << endl;
// 求窗口内最大值, 和最小值对应一下即可
hh = 0, tt = -1;
for (int i = 0; i < n; ++ i)
{
while (hh <= tt && a[q[tt]] <= a[i]) -- tt;
q[++ tt] = i;
if (hh <= tt && q[hh] < i - k + 1) ++ hh;
if (i >= k - 1) cout << a[q[hh]] << ' ';
}
return 0;
}