剑指 Offer 59 - I. 滑动窗口的最大值

发布时间 2023-04-13 20:04:14作者: lxy_cn

题目链接:剑指 Offer 59 - I. 滑动窗口的最大值

方法一:栈模拟队列

解题思路

模拟滑动窗口的移动过程,对于每个滑动窗口快速获取其最大值,通过栈模拟队列,可以在 \(O(1)\) 时间复杂度获取最大值。

  • 栈类:
    • 属性:数组存储元素,栈顶但前指针,指向当前最大值的指针,指向前一个最大值的指针数组;
    • 方法:入栈、出栈、是否为空、返回最大值。
    • 入栈:若当前入栈元素大于最大值,那入栈后应该更新当前栈顶指针的前一个最大值指针为当前最大值指针,然后在更新当前最大值指针为栈顶指针;
    • 出栈:若当前栈顶指针就是最大值指针,那么应该替换当前指针的前一个最大值指针为当前最大值指针,然后更新栈顶指针。
  • 队列类:
    • 属性:包含上述两个栈对象,\(stackA\)\(stackB\),其中 \(A\) 负责出队、\(B\) 负责入队;
    • 方法:入队、出队、返回最大值。
    • 入队:直接 \(push\)\(stackB\)
    • 出队:若 \(stackA\) 为空,那么将 \(stackB\) 全部出栈并加入 \(stackA\) 中,然后出栈 \(stackA\);否则直接出栈;
    • 返回最大值:返回两个栈中最大值的最大值。

代码

const int N = 100010;

class Stack {
private:
    int stackElment[N];
    int stackLastMaxElment[N]; // stackLastMaxElment[maxidx] 指当前maxidx的上一个maxidx
    int topidx, maxidx; // 指向当前最大值
public:
    Stack() {
        topidx = -1;
        maxidx = -1;
    }
    int maxValue() {
        if (maxidx != -1) return stackElment[maxidx];
        else return INT_MIN;
    }
    bool isempty() {
        return topidx == -1 ? true : false;
    }
    void push(int x) {
        topidx ++ ;
        stackElment[topidx] = x;
        if (x > this->maxValue()) {
            stackLastMaxElment[topidx] = maxidx;
            maxidx = topidx;
        }
    }
    int pop() {
        int res = stackElment[topidx];
        if (topidx == maxidx) {
            maxidx = stackLastMaxElment[topidx];
        }
        topidx -- ;
        return res;
    }
};
class Queue{
private:
    Stack stackA, stackB;
public:
    int maxValue() {
        return max(stackA.maxValue(), stackB.maxValue());
    }
    void insert(int x) {
        stackB.push(x);
    }
    int pop() {
        if (stackA.isempty()) {
            while (!stackB.isempty()) {
                stackA.push(stackB.pop());
            }
        }
        return stackA.pop();
    }
};
class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        int n = nums.size();
        Queue q;
        vector<int> ans(n - k + 1);
        for (int i = 0; i < k; i ++ ) q.insert(nums[i]);
        ans[0] = q.maxValue();
        for (int i = 1; i + k - 1 < n; i ++ ) {
            q.pop();
            q.insert(nums[i + k - 1]);
            ans[i] = q.maxValue();
        }
        return ans;
    }
};

复杂度分析

时间复杂度:\(O(n)\)
空间复杂度:\(O(n)\)

方法一:单调队列

解题思路

我们可以发现,现假设窗口中有 \(i, j\) 下标,若 \(nums[i] <= nums[j]\),那么该窗口中的最大值一定不会是 \(nums[i]\)
,因此可以将下标 \(i\) 删除,这样我们得到的窗口值,从左到右是单调递减的,每次去当前窗口的最左端为答案即可。

代码

class Solution {
private:
    int q[100010], h = 0, t = -1;
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        int n = nums.size();
        vector<int> ans;
        for (int i = 0; i < n; i ++ ) {
            if (h <= t && i - k + 1 > q[h]) h ++ ; // 保持窗口大小为 k
            while (h <= t && nums[q[t]] <= nums[i]) t -- ; // 删除下标"i"
            q[++ t] = i;
            if (i >= k - 1) ans.push_back(nums[q[h]]);
        }
        return ans;
    }
};

复杂度分析

时间复杂度:\(O(n)\)
空间复杂度:\(O(n)\)