题目
编写程序,解决高难度经典算法“滑动窗口最大值”(Sliding Window Maximum)。给定一个整数数组与滑动窗口大小 k,窗口从最左向最右滑动,返回每个窗口位置中的最大值。利用 std::deque 构建单调双端队列,在 O(N) 线性时间与 O(k) 额外空间内完成计算,分析为什么传统大顶堆 O(N log k) 无法做到这一极致效率。
输入格式:无需外部输入。
输出格式:输出滑动窗口每次滑动时的最大值序列。
数据范围与约定:数组长度 <code>N ≤ 10^5</code>,单调队列双端弹出。
思路与算法
1. 为什么 priority_queue 不够完美
大顶堆可以在 O(1) 取最大值,但当窗口滑出左边界元素时,大顶堆无法在 O(1) 内删除特定位置的元素,只能依赖延迟删除,堆大小可能膨胀至 O(N),单步调整耗时 O(log N)。
2. 单调双端队列的核心约束std::deque 中存储元素在数组中的下标。队列维护两个单调契约:①下标单调递增;②对应数值严格单调递减。因此队列头部 dq.front() 永远是当前窗口内最大的元素下标。
3. 两头清理策略
队尾清理:新元素入队前,把队尾所有对应数值 <= 新元素的下标全部从尾部 pop_back;队头清理:若 dq.front() <= i - k,说明队头最大值已经滑出窗口范围,从头部 pop_front。
完整程序
#include <iostream>
#include <vector>
#include <deque>
std::vector<int> maxSlidingWindow(const std::vector<int>& nums, int k) {
std::vector<int> result;
std::deque<int> dq; // 存储数组索引,保持队列内对应元素单调递减
for (int i = 0; i < static_cast<int>(nums.size()); ++i) {
// 1. 弹出滑出窗口左边界的过期索引
if (!dq.empty() && dq.front() <= i - k) {
dq.pop_front();
}
// 2. 淘汰队尾所有小于当前值的索引,维持单调性
while (!dq.empty() && nums[dq.back()] <= nums[i]) {
dq.pop_back();
}
// 3. 当前索引入队
dq.push_back(i);
// 4. 当窗口形成(i >= k - 1)时,队头即为窗口最大值
if (i >= k - 1) {
result.push_back(nums[dq.front()]);
}
}
return result;
}
int main() {
std::vector<int> nums = {1, 3, -1, -3, 5, 3, 6, 7};
int k = 3;
std::cout << "测试数组: {1, 3, -1, -3, 5, 3, 6, 7}, 窗口大小 k = " << k << "\n";
std::vector<int> maxVals = maxSlidingWindow(nums, k);
std::cout << "各滑动窗口最大值结果序列: ";
for (int val : maxVals) {
std::cout << val << " ";
}
std::cout << "\n";
return 0;
}运行示例
输出:
测试数组: {1, 3, -1, -3, 5, 3, 6, 7}, 窗口大小 k = 3
各滑动窗口最大值结果序列: 3 3 5 5 6 7