题目链接
题目描述
给定一个长度为 的整数数组和一个大小为
的滑动窗口。窗口从数组的最左侧移动到最右侧,每次只移动一个位置。任务是,对于每个窗口位置,找出窗口内所有元素的最大值。
解题思路
这是一个经典的滑动窗口最大值问题。最朴素的想法是遍历每个窗口,再在窗口内寻找最大值,但这样做的时间复杂度为 ,在
和
较大时会超时。
为了高效地解决这个问题,我们可以使用一种特殊的数据结构:单调递减队列。这个队列将帮助我们在均摊 的时间内获取当前窗口的最大值。
我们使用一个双端队列(deque),里面存储的是数组元素的下标,而不是元素值。这个队列需要始终维护一个关键性质:队列中的下标所对应的数组元素是严格单调递减的。这样,队首的下标就永远对应着当前窗口内的最大值。
算法步骤如下:
- 初始化:创建一个空的双端队列
用于存储下标。
- 遍历数组:从左到右遍历输入数组,对于每个元素
: a. 移除队尾元素:在将新元素的下标
入队之前,先从队尾开始检查。如果队列不为空,且队尾下标对应的元素
小于或等于当前元素
,则将队尾下标弹出。重复此过程,直到队尾元素大于当前元素,或队列为空。 * 原因:如果一个较小的元素在一个较大元素的左边,那么只要这个较大元素还在窗口内,那个较小的元素就永远不可能成为窗口最大值。因此,可以安全地将其从候选队列中移除。 b. 新元素入队:将当前元素的下标
加入队尾。 c. 移除队首元素:检查队首的下标
是否已经滑出当前窗口的范围(即
)。如果是,则将其从队首弹出。 d. 记录结果:当遍历的下标
使得第一个完整的窗口形成时(即
),队首的下标
就对应着当前窗口的最大值。记录
。
- 输出:遍历结束后,将所有记录的结果按格式输出。
由于每个元素的下标最多只入队一次、出队一次,因此整个算法的时间复杂度为线性时间 。
代码
#include <iostream>
#include <vector>
#include <deque>
using namespace std;
int main() {
int n, k;
cin >> n >> k;
vector<int> a(n);
for (int i = 0; i < n; ++i) {
cin >> a[i];
}
deque<int> dq;
vector<int> result;
for (int i = 0; i < n; ++i) {
while (!dq.empty() && a[dq.back()] <= a[i]) {
dq.pop_back();
}
dq.push_back(i);
if (dq.front() <= i - k) {
dq.pop_front();
}
if (i >= k - 1) {
result.push_back(a[dq.front()]);
}
}
for (size_t i = 0; i < result.size(); ++i) {
cout << result[i] << (i == result.size() - 1 ? "" : " ");
}
cout << endl;
return 0;
}
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int k = sc.nextInt();
int[] a = new int[n];
for (int i = 0; i < n; i++) {
a[i] = sc.nextInt();
}
Deque<Integer> dq = new ArrayDeque<>();
List<Integer> result = new ArrayList<>();
for (int i = 0; i < n; i++) {
while (!dq.isEmpty() && a[dq.peekLast()] <= a[i]) {
dq.pollLast();
}
dq.offerLast(i);
if (dq.peekFirst() <= i - k) {
dq.pollFirst();
}
if (i >= k - 1) {
result.add(a[dq.peekFirst()]);
}
}
for (int i = 0; i < result.size(); i++) {
System.out.print(result.get(i) + (i == result.size() - 1 ? "" : " "));
}
System.out.println();
}
}
from collections import deque
n, k = map(int, input().split())
a = list(map(int, input().split()))
dq = deque()
result = []
for i in range(n):
while dq and a[dq[-1]] <= a[i]:
dq.pop()
dq.append(i)
if dq[0] <= i - k:
dq.popleft()
if i >= k - 1:
result.append(a[dq[0]])
print(*result)
算法及复杂度
- 算法:单调队列。
- 时间复杂度:
,因为数组中的每个元素最多被推入和弹出队列一次。
- 空间复杂度:
,因为双端队列中最多存储
个元素的下标。