[路飞]前端算法——数据结构篇(二、队列): 单调队列

210 阅读4分钟

RMQ(Range Minimum Query)问题是指在一个给定的序列中,查询某个区间内的最小值。这个问题在算法和数据结构中是一个经典的问题,有许多解决方法,常见的有线段树、树状数组、分块等。

  1. 线段树: 线段树是解决RMQ问题的一种经典方法。它将序列划分成一个个区间,每个节点代表一个区间的最小值。通过构建线段树,可以实现对区间最小值的高效查询和更新。

  2. 树状数组: 树状数组也可以用于解决RMQ问题。通过预处理数组构建树状数组,可以实现对任意区间的最小值查询。

  3. 分块: 分块也是解决RMQ问题的一种方法。将序列划分成若干个块,对每个块预处理得到块内的最小值,然后通过预处理数组和块的最小值实现对任意区间的最小值查询。

  4. ST表(Sparse Table): ST表是一种空间换时间的方法,可以用于解决RMQ问题。通过预处理得到ST表,可以在O(1)的时间复杂度内查询任意区间的最小值。

  5. 其他方法: 除了上述方法,还有一些其他方法可以解决RMQ问题,如RMQ问题的离线查询、动态规划等。

选择合适的解决方法取决于具体的问题和数据规模。如果需要频繁查询不同区间的最小值,并且数据规模较大,通常会选择线段树、树状数组或ST表等方法;如果数据规模较小或者只需要查询一次最小值,可以考虑使用分块或动态规划等方法。

我们今天要说的是一种特定的RMQ问题,滑动窗口最大值

239.滑动窗口最大值

WX20240507-172007@2x.png

单调队列(Monotonic Queue)是一种特殊的队列数据结构,我们通常用它来解决滑动窗口的最小值问题,它具有以下特点:

  1. 单调性质:单调队列中的元素具有单调性,即满足递增或递减的特性。在单调递增队列中,队首元素是队列中的最小值;在单调递减队列中,队首元素是队列中的最大值。

  2. 队列性质:单调队列仍然是一个队列,它支持入队(enqueue)和出队(dequeue)操作。但与普通队列不同的是,单调队列在入队和出队操作中会维护单调性质。

  3. 应用场景:单调队列通常用于解决一些特定问题,如滑动窗口最大值、最小值等问题,因为它可以在队列中保持特定的单调性质,从而在处理问题时具有较高的效率。

单调队列的实现可以使用双端队列(Deque)来实现,通过合适的操作保持队列的单调性质。以下是单调队列的一种常见实现方式,用于求解滑动窗口最大值(Sliding Window Maximum)的问题:

function maxSlidingWindow(nums, k) {
    const result = [];
    const queue = []; // 单调递减队列,存储元素索引

    for (let i = 0; i < nums.length; i++) {
        // 当队列非空且当前元素大于队尾元素时,弹出队尾元素
        while (queue.length > 0 && nums[i] > nums[queue[queue.length - 1]]) {
            queue.pop();
        }

        // 当队列非空且队首元素不在滑动窗口范围内时,弹出队首元素
        while (queue.length > 0 && queue[0] <= i - k) {
            queue.shift();
        }

        queue.push(i); // 入队当前元素索引

        // 当窗口长度达到 k 时,记录队首元素(当前窗口的最大值)
        if (i >= k - 1) {
            result.push(nums[queue[0]]);
        }
    }

    return result;
}

// 示例用法
const nums = [1, 3, -1, -3, 5, 3, 6, 7];
const k = 3;
console.log(maxSlidingWindow(nums, k)); // 输出滑动窗口最大值的结果

这段代码实现了一个单调递减队列,用于求解滑动窗口最大值的问题。在处理每个元素时,保持队列的单调递减性质,同时在滑动窗口的范围内找到最大值并记录下来。这种单调队列的应用在解决一些算法问题中具有很高的效率和实用性。