每日c/c++题 备战蓝桥杯(P1886 滑动窗口 /【模板】单调队列)

洛谷P1886 滑动窗口【模板】单调队列详解

题目描述

给定一个长度为n的整数序列,要求输出所有长度为k的连续子数组的:

  1. 最小值(第一部分输出)
  2. 最大值(第二部分输出)

数据范围:

  • 1 ≤ k ≤ n ≤ 10^6
  • 时间限制:1s
  • 空间限制:128MB

算法思路

本题是单调队列模板题,核心思想是通过维护一个双端队列来高效获取滑动窗口的极值。

暴力法缺陷

直接对每个窗口遍历求极值的时间复杂度为O(nk),当n=1e6时会超时。需要优化到O(n)时间复杂度。

单调队列原理

维护一个双向队列,保证队列元素按单调顺序排列:

  • 最小值队列:保持升序(队头到队尾递增)
  • 最大值队列:保持降序(队头到队尾递减)

当新元素入队时:

  1. 移除所有比当前元素大的元素(最小值队列)或小的元素(最大值队列)
  2. 将当前元素加入队尾
  3. 移除所有超出窗口范围的队头元素

代码解析

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;

const int MAXN = 1e6 + 10;
int n, k;
int a[MAXN];         // 原始数组
int que[MAXN];        // 单调队列
int ffront, bback;    // 队列头尾指针

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    
    cin >> n >> k;
    for(int i = 1; i <= n; ++i) cin >> a[i];

    // 处理最小值部分
    ffront = 1, bback = 0;
    for(int i = 1; i <= n; ++i) {
        // 移除超出窗口的队头元素
        while(ffront <= bback && que[ffront] < i - k + 1) ffront++;
        
        // 维护单调性:移除所有>=当前值的队尾元素
        while(ffront <= bback && a[que[bback]] >= a[i]) bback--;
        que[++bback] = i;
        
        // 窗口形成后输出结果
        if(i >= k) cout << a[que[ffront]] << " ";
    }
    
    cout << "\n";
    
    // 处理最大值部分
    ffront = 1, bback = 0;
    for(int i = 1; i <= n; ++i) {
        while(ffront <= bback && que[ffront] < i - k + 1) ffront++;
        
        // 维护单调性:移除所有<=当前值的队尾元素
        while(ffront <= bback && a[que[bback]] <= a[i]) bback--;
        que[++bback] = i;
        
        if(i >= k) cout << a[que[ffront]] << " ";
    }
    
    return 0;
}

关键点说明

  1. 队列初始化

    • 使用数组模拟双端队列,ffront指向队头,bback指向队尾
    • 初始时队列为空(ffront > bback
  2. 窗口维护

    • 窗口有效性检查que[ffront] < i - k + 1判断队头是否已出窗口
    • 单调性维护 :通过while循环移除不符合单调性的队尾元素
  3. 时间复杂度

    • 每个元素最多入队出队各一次,总时间复杂度O(n)

算法对比

方法 时间复杂度 空间复杂度 适用场景
暴力法 O(nk) O(1) n≤1e4
优先队列 O(nlogk) O(k) 需要动态极值但可接受log
单调队列 O(n) O(k) 滑动窗口极值问题

注意事项

  1. 数组越界:使用1-based索引避免边界判断错误
  2. 队列为空:本题保证k≤n,无需额外处理空队列情况
  3. 输出格式:注意两部分输出间的换行符

扩展应用

单调队列思想可应用于:

  1. 动态规划优化(如DP滑动窗口技巧)
  2. 实时数据流处理
  3. 股票买卖问题(求某段时间内的最大利润)

总结

本题通过单调队列将滑动窗口极值问题的时间复杂度优化到线性级别。核心在于理解:

  • 如何维护队列的单调性
  • 如何高效移除过期元素
  • 如何处理窗口滑动时的边界情况

掌握此模板可解决LeetCode 239、剑指Offer 59等经典题目。

相关推荐
磨十三6 小时前
C++ 标准库排序算法 std::sort 使用详解
开发语言·c++·排序算法
湫兮之风8 小时前
C++: Lambda表达式详解(从入门到深入)
开发语言·c++
奔跑吧邓邓子9 小时前
【C++实战(54)】C++11新特性实战:解锁原子操作与异步编程的奥秘
c++·实战·c++11新特性·原子操作·异步编程
Sylvia-girl9 小时前
C语言中经常使用的函数
c语言·开发语言
Mr_WangAndy9 小时前
C++设计模式_结构型模式_适配器模式Adapter
c++·设计模式·适配器模式·c++设计模式
bkspiderx9 小时前
C++设计模式之结构型模式:代理模式(Proxy)
c++·设计模式·代理模式
Cauhele浅能10 小时前
【嵌入式C快捷键设计】表驱动法实现
c语言·设计模式
润 下10 小时前
C语言——深入理解函数声明定义和调用访问
c语言·开发语言·经验分享·笔记·程序人生·其他
bkspiderx11 小时前
C++设计模式之行为型模式:解释器模式(Interpreter)
c++·设计模式·解释器模式
范纹杉想快点毕业11 小时前
单片机开发中的队列数据结构详解,队列数据结构在单片机软件开发中的应用详解,C语言
c语言·数据库·stm32·单片机·嵌入式硬件·mongodb·fpga开发