滑动窗口最大值:单调队列

字数: 483

题目

力扣: 239. 滑动窗口最大值

题解

遍历窗口可以考虑暴力解法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
std::vector<int> maxSlidingWindow_brute(std::vector<int> &nums, int k)
{
    std::vector<int> res;
    int i, window_max;
    for (i = 0; i <= nums.size() - k; i++)
    {
        window_max = nums[i];
        for (int j = i; j < i + k; j++)
        {
            if (window_max < nums[j])
                window_max = nums[j];
        }
        res.push_back(window_max);
    }
    return res;
}

但是暴力解法在数据量大的情况下是无法接受的。

比较优秀的算法是使用单调队列的方式:

单调队列

“如果一个选手比你小还比你强,你就可以退役了。” ——单调队列原理

单调队列主要就是用来解决滑动窗口类问题的数据结构,可以做到 $O(N)$ 的时间复杂度。
其基本思想是维护一个双端队列,遍历序列,仅当一个元素可能成为某个区间最值时才保留它。

对于滑动窗口最大值问题在遍历序列主要是三步骤:

  1. 移除队尾所有小于当前值的元素
  2. 移除窗口左边界外的队头元素
  3. 窗口形成后,队头即为当前窗口的最大值
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
std::vector<int> maxSlidingWindow(std::vector<int> &nums, int k)
{
    std::deque<int> dq;
    std::vector<int> res;
    int i;

    if (nums.empty() || k <= 0)
        return std::vector<int>{};
    for (i = 0; i < nums.size(); i++)
    {
        // 移除队尾所有小于当前值的元素
        while (!dq.empty() && nums[dq.back()] <= nums[i])
            dq.pop_back();

        dq.push_back(i);

        // 移除窗口左边界外的队头元素
        if (dq.front() <= i - k)
            dq.pop_front();

        // 窗口形成后,队头即为当前窗口的最大值
        if (i >= k - 1)
            res.emplace_back(nums[dq.front()]);
    }
    return res;
}

参考资料

  1. bilibili 滑动窗口最大值 | 编程面试必刷题
  2. 知乎 算法学习笔记(66): 单调队列