专栏文章

单调队列

算法·理论参与者 1已保存评论 0

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
0 条
当前快照
1 份
快照标识符
@miq7mtsm
此快照首次捕获于
2025/12/04 00:17
3 个月前
此快照最后确认于
2025/12/04 00:17
3 个月前
查看原文
单调队列是一种基于双端队列实现的高效数据结构,用于维护区间内的单调性,尤其适合处理滑动窗口最值问题。其核心思想是通过动态调整队列元素,确保队列内元素严格有序且满足特定窗口约束。以下从定义、应用场景及算法步骤三方面详细阐述:

一、定义与特性

单调队列是一种限制只能队尾插入,但允许两端删除的双端队列。队列内元素从队首到队尾呈严格单调性(递增或递减)。例如,维护最大值时队列为单调递减,最小值时为递增。其关键特性包括:
  • 保序性:队列内元素的顺序与原序列严格一致,确保窗口覆盖范围的正确性。
  • 高效操作:插入、删除、查询最值的时间复杂度均为 O(1)$$(均摊分析))
  • 下标存储:通常存储原序列的下标而非值,便于判断元素是否超出窗口范围。
例如,在滑动窗口最大值问题中,单调队列通过动态剔除冗余元素,始终保证队首为当前窗口最大值下标。

二、典型应用场景

  1. 滑动窗口最值问题
    LeetCodeLeetCode 239239题,给定数组和窗口大小kk,求每个窗口的最大值。单调队列通过以下步骤高效解决:
    • 插入时剔除比新元素小的队尾元素,保持单调递减性。
    • 删除超出窗口范围的队首元素,确保窗口有效性。
    • 队首即为当前窗口最大值,时间复杂度为O(n)O(n)
  2. 动态规划优化
    在需要区间转移的DP问题中,单调队列可优化状态转移效率。例如:
    • 选择数字问题(洛谷P2034)(洛谷P2034):限制连续选择不超过kk个数,求最大和。通过定义状态转移方程并维护单调队列,快速筛选最优前驱状态,将复杂度从O(nk)O(nk)降至O(n)O(n)
    • 最短子数组和问题(LeetCode862)(LeetCode 862):求满SumkSum \geqslant k的最短子数组。通过前缀和数组结合单调队列,避免重复计算,高效定位可行解。
  3. 多维区间最值
    如处理矩阵中的n×nn\times n子矩阵最值问题时,先对每行用单调队列求滑动窗口最值,再对结果矩阵的每列再次处理,最终得到全局最优解。

三、算法实现步骤

以下以维护单调递减队列(求最大值)为例,说明核心步骤:
步骤操作时间复杂度
初始化队列创建空双端队列,存储元素下标。O(1)O(1)
维护队尾单调性插入新元素i时,循环删除队尾所有对应值\leq当前值的元素,保证队列递减。均摊O(1)O(1)
维护队首有效性若队首元素下标超出当前窗口左边界(如ifrontki - front \geqslant k),则删除队首元素。O(1)O(1)
记录结果当窗口完整形成(如ik1i\geqslant k-1时),队首元素即为当前窗口最大值。O(1)
代码示例(滑动窗口最大值):
CPP
#include <cstdio>
#include <deque>
using namespace std;

// 首先创建一个队列,并以第一个窗口中的元素进行初始化
deque<int> window;
for(int i = 0; i < k; ++ i) window.push_back(arr[i]);
// 每轮遍历队首元素出栈,并从队尾入队一个元素
for(int i = k; i < n - k + 1; ++ i)
{
    window.pop_front();
    window.push_back(arr[i]);
    // 查找当前队列中的最大值
    int max = window.front();
    for(int item : window) if(item > max) max = item;
    printf("%d ", max);
}


四、性能对比

方法时间复杂度空间复杂度适用场景
暴力枚举O(nk)O(nk)O(1)O(1)小规模数据
单调队列O(n)O(n)O(k)O(k)大规模数据,实时处理
线段树/STSTO(nlogn)O(n \log n)O(nlogn)O(n \log n)静态区间查询,多次询问
从表格可见,单调队列在动态窗口问题中具有显著优势,尤其适合处理数据流或实时性要求高的场景。

评论

0 条评论,欢迎与作者交流。

正在加载评论...