每日c/c++题 备战蓝桥杯(洛谷P1440 求m区间内的最小值 详解(单调队列优化))

洛谷P1440 求m区间最小值:单调队列优化详解(从暴力到O(n)的蜕变)


tags: [算法, 数据结构, 滑动窗口, 洛谷, C++]

引言

在处理序列数据的区间查询问题时,暴力枚举往往难以应对大规模数据。本文以洛谷P1440为切入点,深入剖析如何通过单调队列数据结构,将时间复杂度从O(nm)优化至O(n),完美解决百万级数据下的实时查询需求。

题目解析

问题定义

给定长度为n的序列和滑动窗口大小m,要求输出每个窗口内的最小值。当m>n时输出全0。

输入格式

  • 第一行:两个整数n, m
  • 第二行:n个整数表示序列元素

输出格式

输出n-m+1行,每行一个整数表示对应窗口的最小值

数据范围

  • 1 ≤ m ≤ n ≤ 2×10⁶
  • 元素值域:1 ≤ a_i ≤ 10⁹

算法演进:从暴力到优化

暴力解法(O(nm))
cpp 复制代码
for (int i=0; i<=n-m; i++) {
    int min_val = INF;
    for (int j=i; j<i+m; j++) {
        min_val = min(min_val, a[j]);
    }
    cout << min_val << endl;
}

缺陷:当n=2×10⁶时,总操作次数达4×10¹²次,远超时限。

单调队列优化(O(n))

核心思想:维护一个双端队列,使其满足:

  1. 队列元素对应值严格单调递增
  2. 队首元素始终是当前窗口的最小值下标
  3. 自动清理失效元素(超出窗口范围或被新元素"淘汰")

代码实现与优化

完整代码(C++)
cpp 复制代码
#include <iostream>
using namespace std;

const int MAXN = 2e6 + 5;
int a[MAXN];       // 原始数组(1-based)
int deque[MAXN];    // 手动实现双端队列
int front = 0, rear = -1;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    int n, m;
    cin >> n >> m;
    
    // 特殊情况处理
    if (m > n) {
        for (int i=0; i<n; i++) cout << "0\n";
        return 0;
    }

    for (int i=1; i<=n; i++) cin >> a[i];

    // 初始化队列(前m个元素)
    for (int i=1; i<=m; i++) {
        while (rear >= front && a[deque[rear]] >= a[i]) rear--;
        deque[++rear] = i;
    }

    // 处理剩余窗口
    for (int i=m+1; i<=n; i++) {
        cout << a[deque[front]] << "\n";
        
        // 移除失效队首
        while (deque[front] <= i - m) front++;
        
        // 维护单调性
        while (rear >= front && a[deque[rear]] >= a[i]) rear--;
        deque[++rear] = i;
    }
    
    // 输出最后一个窗口
    cout << a[deque[front]] << "\n";

    return 0;
}
关键优化点
  1. 输入优化

    • 关闭同步流ios::sync_with_stdio(false)
    • 解除cin绑定cin.tie(nullptr)
      使输入速度提升3-5倍
  2. 数组下标处理

    • 采用1-based索引,避免处理0号元素的特殊情况
    • 显式处理m>n的边界条件
  3. 空间优化

    • 使用静态数组模拟双端队列,避免STL动态内存分配的开销
    • 相比STL deque,内存访问更连续,缓存命中率更高

算法流程详解

以输入序列[1,3,2,4,5], m=3为例:

窗口位置 队列状态(下标) 最小值 操作说明
[1,3,2] 1 → 3 → 2 1 初始化队列
[3,2,4] 2 → 4 2 移除过期1号元素,加入4
[2,4,5] 2 → 4 → 5 2 维护单调性,加入5

执行流程

  1. 初始化阶段:处理前m个元素,构建初始单调队列
  2. 滑动阶段
    • 输出当前窗口最小值(队首元素)
    • 移除窗口左边界外的元素
    • 从队尾开始移除所有≥新元素的元素,保持单调性
    • 将新元素下标加入队列

复杂度分析

指标 复杂度 说明
时间复杂度 O(n) 每个元素最多入队、出队各一次
空间复杂度 O(m) 队列长度不超过窗口大小

常见问题解决方案

  1. 队列为空检查

    通过while (rear >= front)保证队列操作安全性

  2. 重复元素处理

    使用>=而非>判断,确保相同值元素正确弹出,维护严格单调性

  3. 窗口左移边界

    deque[front] <= i - m时,队首元素已不在当前窗口

扩展应用场景

  1. 滑动窗口最大值:修改比较符号方向即可
  2. 动态规划优化:如最大矩形面积问题(LeetCode 84)
  3. 实时流处理:维护最近m条记录的统计信息

总结

单调队列通过三个核心操作:

  • 失效元素清理:保证窗口有效性
  • 单调性维护:确保最小值在队首
  • 元素淘汰机制:避免无效比较

实现了时间复杂度的质的飞跃。该算法思想是解决滑动窗口类问题的基石,掌握其精髓对算法进阶至关重要。建议配合LeetCode 239(滑动窗口最大值)进行巩固练习。

"优秀的算法工程师不是在发明新算法,而是在合适场景选择最优雅的工具。" ------ 本题解正是对这一理念的实践诠释。

相关推荐
CodeWithMe22 分钟前
【C/C++】现代C++线程池:从入门到生产级实现
c++
lsnm26 分钟前
【LINUX操作系统】生产者消费者模型(下):封装、信号量与环形队列
linux·运维·服务器·开发语言·c++·ubuntu·centos
laimaxgg1 小时前
五、central cache的设计
c++·缓存·性能优化
虾球xz2 小时前
游戏引擎学习第289天:将视觉表现与实体类型解耦
c++·学习·游戏引擎
liuzhangfeiabc2 小时前
[luogu12542] [APIO2025] 排列游戏 - 交互 - 博弈 - 分类讨论 - 构造
c++·算法·题解
黑塞1233 小时前
C++ Pimpl(Pointer to Implementation)设计思想
c++
byte轻骑兵3 小时前
【Bluedroid】蓝牙HID Device virtual_cable_unplug全流程源码解析
c++·hid·bluedroid
四谷夕雨3 小时前
C++数据结构——红黑树
开发语言·数据结构·c++
小王努力学编程3 小时前
【数据库课程设计】网上投票管理系统
数据库·c++·qt·课程设计
oscar9994 小时前
1.3 C++之变量与数据类型
开发语言·c++