[剑指-Offer] 59 - I. 滑动窗口的最大值及II. 队列的最大值(模拟、单调队列、常规解法)

1. 题目来源

链接:I. 滑动窗口的最大值
链接:II. 队列的最大值
来源:LeetCode——《剑指-Offer》专项

2. 题目说明

在这里插入图片描述
在这里插入图片描述

3. 题目解析 — I. 滑动窗口的最大值

方法一:模拟+单调队列+常规解法

实际考查 单调队列,若采用暴力法,需要 O ( k ) O(k) 的时间复杂度找到滑动窗口的最大值,对于长度为 n 的输入数组,暴力求解算法复杂度为 O ( n k ) O(nk)

我们能发现,这个滑动窗口就是一个队列,满足先进先出的特性,而我们需要找到队列的最大值,该问题就能解决了。

我们在 [剑指-Offer] 30. 包含min函数的栈(边界情况,代码优化) 采用 O ( 1 ) O(1) 的时间得到了栈的最大值,并且在 [剑指-Offer] 9. 用两个栈实现队列(栈、队列、模拟) 中,也用栈模拟实现了队列。那么这个问题至此,我们能解决了。


下面来介绍采用 deque 的做法思想:

  • 插入前 k 个数据
    • 首先若 deque 为空,那么就直接尾插进第一个数据
    • 插入第二个数据时,队列非空,若该数字比队尾数字大,那么队尾数字就不可能成为滑动窗口的最大值,那么就将队尾数据进行出队操作。持续上述操作,直到队列为空或者遇到队列中比它大的数据,再将数据插入。其实是寻找前 k 个数据中的最大值,作为队首元素
  • 插入 k+1 至后面所有数据
    • 在插入 k+1 的时候,首先将队列中上一个滑动窗口的最大值放进创建的 vt 中,即需要将队首元素返回
    • 如果此时队列不为空,并且待插入数据比队尾元素大的话,说明队尾元素不可能是该滑动窗口的最大值了,即需要队尾出队。持续上述操作,直到队列为空或者遇到队列中比它大的数据,再将数据插入。其实是寻找前 k 个数据中的最大值,添加成为队列元素
    • 到达此步需要进行队首元素是否在当前滑动窗口的判断。即如果此时队列不为空,队首元素是最大值,但是可能是上一个滑动窗口的最大值,其根本就不在当前窗口内,需要将队首数据进行出队。判断的方式比较简单,就是单纯的待入队元素下标减去队首下标,查看其差值是否大于 k 即可。所以为了方便,我们在 deque 存放的是数组的下标。 这个需要额外注意
  • 最后将队首元素添加进入数组 vt ,并返回 vt 即可

这个其实就是一般的 单调队列 数据结构的抽象。

参见代码如下:

// 执行用时 :12 ms, 在所有 C++ 提交中击败了99.62%的用户
// 内存消耗 :17.6 MB, 在所有 C++ 提交中击败了100.00%的用户

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        if (nums.size() < k or k <= 0) return {};
        vector<int> vt;
        deque<int> dq;
        for (int i = 0; i < k; ++i) {
            while (!dq.empty() and nums[i] >= nums[dq.back()]) dq.pop_back();
            dq.push_back(i);
        }
        for (int i = k; i < nums.size(); ++i) {
            vt.push_back(nums[dq.front()]);
            while (!dq.empty() and nums[i] >= nums[dq.back()]) dq.pop_back();
            if (!dq.empty() and dq.front() <= i - k) dq.pop_front();
            dq.push_back(i);
        }
        vt.push_back(nums[dq.front()]);
        return vt;
    }
};

4. 题目解析 — II. 队列的最大值

方法一:模拟+单调队列+常规解法

采用上个问题滑动窗口的思想,创建 queue 再以辅助 deque 进行存储最大值,很容易实现这个带 max 函数的队列,主要思路如下:

  • queue 就是正常的队列,负责 pushpop
  • deque 用来存放最大值
  • 如果新的 value > deque.back(),那么 deque 一直进行 pop_back 操作,直到尾端的值大于等于 value 或者为空
  • 再将 value 压入 deque 的尾部
  • 每次取 max_value,返回 deque 首部的值
  • queue 进行 pop 操作时,如果 que 首部的值等于 deque 首部的值,那么 deque 同样需要进行 pop_front 操作。否则仅需 queue 出队即可,deque 队首元素仍是 queue 的最大值

这是一个模拟实现,也是单调队列基础。

参见代码如下:

// 执行用时 :180 ms, 在所有 C++ 提交中击败了36.69%的用户
// 内存消耗 :50.3 MB, 在所有 C++ 提交中击败了100.00%的用户

class MaxQueue {
public:
    MaxQueue() {

    }
    
    int max_value() {
        return que.empty() ? -1 : dq.front();
    }
    
    void push_back(int value) {
        que.push(value);
        while (!dq.empty() and dq.back() < value) dq.pop_back();
        dq.push_back(value);
    }
    
    int pop_front() {
        if(que.empty()) return -1;
        int t = que.front();
        que.pop();
        if(t == dq.front()) dq.pop_front();
        return t;
    }
private:
    queue<int> que;
    deque<int> dq;
};

/**
 * Your MaxQueue object will be instantiated and called as such:
 * MaxQueue* obj = new MaxQueue();
 * int param_1 = obj->max_value();
 * obj->push_back(value);
 * int param_3 = obj->pop_front();
 */
发布了343 篇原创文章 · 获赞 197 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/yl_puyu/article/details/104784840
今日推荐