链接:https://ac.nowcoder.com/acm/problem/50528
来源:牛客网
题目描述
给一个长度为N的数组,一个长为K的滑动窗体从最左端移至最右端,你只能看到窗口中的K个数,每次窗体向右移动一位。
你的任务是找出窗体在各个位置时的最大值和最小值。
输入描述:
第1行:两个整数N和K;
第2行:N个整数,表示数组的N个元素(≤2×109);
输出描述:
第一行为滑动窗口从左向右移动到每个位置时的最小值,每个数之间用一个空格分开;
第二行为滑动窗口从左向右移动到每个位置时的最大值,每个数之间用一个空格分开。
示例1
输入
8 3
1 3 -1 -3 5 3 6 7
输出
-1 -3 -3 -3 3 3
3 3 5 5 6 7
备注:
对于20%的数据,K≤N≤1000;
对于50%的数据,K≤N≤105;
对于100%的数据,K≤N≤106;
这道题对时间和空间都不是很友好,是一道单调队列的模板题,但其实线段树也可以解决只不过需要一些修改。一般的线段树定义如下:
struct SegTree {
int l, r; //该节点管理的范围[l,r]
int val; //存放的数据
}ST[MAXN << 2];//MAXN * 4
可以看出构建线段树所需的空间很多肯定会MLE,其实仔细想一想结构体里的 l 和 r 真的是必要的么?其实不是。
我们可以省略 l 与 r 仅仅只将其当做一个一维数组。
开始的时候根节点一定管理着[1 , n]而它的子节点管理的区间一定是父节点的左一半和右一半这样想是不是 l , r 本身就是可有可无的呢?只要将节点管理的区间作为参数传入即可。
#include<iostream>
#include<algorithm>
#include<limits.h>
using namespace std;
const int maxn = 1000005;
struct SegTree {
int val;//数值节点存放的数据
}ST[maxn << 2];
int N, K;
int cnt = 1;
int a[maxn];
void ST_MAX_OR_MIN(int index, bool state) {
if (state) ST[index].val = max(ST[index << 1].val, ST[index << 1 | 1].val);
else ST[index].val = min(ST[index << 1].val, ST[index << 1 | 1].val);
}
void Build(int l, int r,int index, bool state) {
if (l == r) {
ST[index].val = a[cnt];
cnt += 1;
return;
}
Build(l, (l + r) >> 1,index << 1, state);
Build(((l + r) >> 1) + 1, r, (index << 1) | 1, state);
ST_MAX_OR_MIN(index, state);
}
int ST_Query(int l, int r, int L, int R, int index, bool state) {
//建立区间[l,r],当前节点管理的区间[L,R]
if (R < l || L > r) return state ? INT_MIN : INT_MAX;
if (L >= l && R <= r) {//待查区间包含了该节点管理的区间,直接返回
return ST[index].val;
}
if (state) return max(ST_Query(l, r, L, (L + R) >> 1, index << 1, state), ST_Query(l, r, ((L + R) >> 1) + 1, R, index << 1 | 1, state));
else return min(ST_Query(l, r, L, (L + R) >> 1, index << 1, state), ST_Query(l, r, ((L + R) >> 1) + 1, R, index << 1 | 1, state));
}
int main() {
cin >> N >> K;
for (int i = 1; i <= N; i++) scanf("%d",&a[i]);
Build(1, N, 1, 0);
for (int i = 1; i <= N - K + 1; i++) {
printf("%d ",ST_Query(i, i + K - 1, 1, N, 1, 0));
}
putchar(10);
cnt = 1;
//第二次建树输出最大值
Build(1, N, 1, 1);
for (int i = 1; i <= N - K + 1; i++) {
printf("%d ",ST_Query(i, i + K - 1, 1, N, 1, 1));
}
return 0;
}
~~
另外单调队列的代码(有注释):
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
const int maxn = 1000005;
deque<int> dq;//队列用于存放元素的下标
int a[maxn];
int N, K;
int main() {
cin >> N >> K;
for (int i = 1; i <= N; i++) scanf("%d", a + i);
for (int i = 1; i <= N; i++) {
while (dq.size() > 0 && i - dq.front() >= K) dq.pop_front();
//如果当前窗口已经离开队头所在位置队头出队
while (dq.size() > 0 && a[dq.front()] > a[i]) dq.pop_front();
//如果插入的元素小于队头元素说明队头元素不可能为当前窗口内的最小值,队头元素出队
while (dq.size() > 0 && a[dq.back()] > a[i]) dq.pop_back();
//如果插入的元素小于队尾元素说明该元素也不可能为当前窗口的最小值,队尾元素出队
dq.push_back(i);
if (i >= K) printf("%d ", a[dq.front()]);
}
putchar(10);
dq.clear();//第二次求最大的与求最小的操作类似
for (int i = 1; i <= N; i++) {
while (dq.size() > 0 && i - dq.front() >= K) dq.pop_front();
while (dq.size() > 0 && a[dq.front()] < a[i]) dq.pop_front();
while (dq.size() > 0 && a[dq.back()] < a[i]) dq.pop_back();
dq.push_back(i);
if (i >= K) printf("%d ", a[dq.front()]);
}
return 0;
}
哎,貌似使用双端队列存放元素下标比较容易写,想了想还是没想到如果直接存放元素该怎么处理~