RMQ 问题
- 在讨论单调队列问题前,我们先看下 RMQ 问题
- RMQ(x, y) 就是询问数组在区间 [x, y] 内的最小值
- 现有数组如下,index 为数组索引值,value 为索引对应的元素值
- index:0 1 2 3 4 5 6 7
- value:3 1 4 5 2 9 8 12
- 观察数组可以得到,RMQ(0, 3) = 1,RMQ(3, 7) = 2
- 如果固定询问区间的尾部元素值,例如:RMQ(x, 7),那么最少记录上面数组中的几个元素,就可以满足任意元素值的 x 的 RMQ(x, 7) 需求
- 由上述描述可以推算得出,最少记录
1,2,8,12
这四个元素即可满足 - 因为不管元素 x 值为几,RMQ(x, 7) 的结果都在这四个元素中
单调队列
- 很明显
1,2,8,12
是一个相对位置不变的单调递增的序列 - 其实单调队列本质就是为了来
维护区间最值问题
的,也就是 RMQ 问题 - 维护规则如下:
- 入队操作:
- 新元素从队尾入队,同时会将队列中已存在的,会破坏单调性的元素,从队尾依次移除,以维持队列元素的单调性
- 出队操作:
- 如果队首元素已经不在区间范围内,就将队首元素出队
- 元素性质:
- 队首元素始终是当前维护区间的最(大/小)值
- 入队操作:
- 所以单调队列维护的元素,就是上述固定末尾的 RMQ 问题结果的最少元素
代码示例
- 现有如下问题
- 给出一个长度为 n 的数组 list,还有一个长为 k 的滑动窗口从数组最左移动到最右,每次窗口移动一个元素的距离,同时输出窗口中的最小值
- 下面代码中
- min 表示每次滑动窗口中的最小值组成的序列,定义单调队列 q,为方便计算队首元素是否超出窗口的范围,队列只用存对应元素的下标即可
- 扫描每个元素,并进行入队操作,同时维护 q 的单调性
- 如果滑动窗口滑过 队首元素指向的位置 刚好一格,则需要将队首元素进行出队操作
/**
*
* @param {*} list 原始数组
* @param {*} k 滑动窗口的大小
*/
const slideWindow = (list, k) => {
let min = [];
let q = [];
for (let i = 0; i < list.length; i++) {
// 维护 q 的单调性
while (q.length && list[q.length - 1] > list[i]) q.pop();
q.push(i);
if (i - q[0] === k) q.shift();
if (i + 1 < k) continue; // 此时还没有扫描到 k 个元素,还没扫描到窗口中的所有元素
min.push(list[q[0]]);
}
console.log(min.join());
};
slideWindow([1, 3, -1, -3, 5, 3, 6, 7], 3); // -1,-3,-3,-3,3,3
小结
- 上面简单介绍了 RMQ 问题,以及单调队列所维护元素的本质
- 下篇文章,我们将介绍单调栈的相关知识点
“ 本文正在参加「金石计划 . 瓜分6万现金大奖」 ”