滑动窗口最大值

115 阅读1分钟

1、题目描述

给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。

返回滑动窗口中的最大值。

进阶:

你能在线性时间复杂度内解决此题吗?

提示:

  • 1 <= nums.length <= 10^5
  • -10^4 <= nums[i] <= 10^4
  • 1 <= k <= nums.length

2、思路

一个普通的队列一定有这两个操作: 一个「单调队列」的操作也差不多:

class Queue {
    // enqueue 操作,在队尾加入元素 n
    void push(int n);
    // dequeue 操作,删除队头元素
    void pop();
}
class MonotonicQueue {
    // 在队尾添加元素 n
    void push(int n);
    // 返回当前队列中的最大值
    int max();
    // 队头元素如果是 n,删除它
    void pop(int n);
}

当然,这几个 API 的实现方法肯定跟一般的 Queue 不一样,不过我们暂且不管,而且认为这几个操作的时间复杂度都是 O(1),先把这道「滑动窗口」问题的解答框架搭出来:

int[] maxSlidingWindow(int[] nums, int k) {
    MonotonicQueue window = new MonotonicQueue();
    List<Integer> res = new ArrayList<>();
 
    for (int i = 0; i < nums.length; i++) {
        if (i < k - 1) {
            //先把窗口的前 k - 1 填满
            window.push(nums[i]);
        } else {
            // 窗口开始向前滑动
            // 移入新元素
            window.push(nums[i]);
            // 将当前窗口中的最大元素记入结果
            res.add(window.max());
            // 移出最后的元素
            window.pop(nums[i - k + 1]);
        }
    }
    // 将 List 类型转化成 int[] 数组作为返回值
    int[] arr = new int[res.size()];
    for (int i = 0; i < res.size(); i++) {
        arr[i] = res.get(i);
    }
    return arr;
}

观察滑动窗口的过程就能发现,实现「单调队列」必须使用一种数据结构支持在头部和尾部进行插入和删除,很明显双链表是满足这个条件的。

「单调队列」的核心思路和「单调栈」类似,push方法依然在队尾添加元素,但是要把前面比自己小的元素都删掉:

class MonotonicQueue {
    // 双链表,支持头部和尾部增删元素
    private LinkedList<Integer> q = new LinkedList<>();
 
    public void push(int n) {
    // 将前面小于自己的元素都删除
        while (!q.isEmpty() && q.getLast() < n) {
            q.pollLast();
        }
        q.addLast(n);
    }
}

你可以想象,加入数字的大小代表人的体重,把前面体重不足的都压扁了,直到遇到更大的量级才停住。

image.png 如果每个元素被加入时都这样操作,最终单调队列中的元素大小就会保持一个单调递减的顺序,因此我们的max方法可以可以这样写:

public int max() {
    // 队头的元素肯定是最大的
    return q.getFirst();
}
pop方法在队头删除元素n,也很好写:

public void pop(int n) {
    if (n == q.getFirst()) {
        q.pollFirst();
    }
}

之所以要判断data.front() == n,是因为我们想删除的队头元素n可能已经被「压扁」了,可能已经不存在了,所以这时候就不用删除了:

至此,单调队列设计完毕,看下完整的解题代码:

class Solution {
    LinkedList<Integer> window = new LinkedList<>();
    public int[] maxSlidingWindow(int[] nums, int k) {
        List<Integer> res = new ArrayList<>();
        for(int i = 0; i < nums.length;i++){
            if(i < k-1){
                push(nums[i]);
            }
            else{
                push(nums[i]);
                res.add(window.getFirst());
                pop(nums[i-k+1]);
            }
        }
        int[] array = new int[res.size()];
        for(int i = 0;i < array.length;i++){
            array[i] = res.get(i);
        }
        return array;
    }
    public void push(int n){
        while(!window.isEmpty() && window.getLast() < n){
            window.removeLast();
        }
        window.add(n);
    }
    public void pop(int n){
        if(window.getFirst() == n){
            window.remove();
        }
    }
    public int max(){
        return window.getFirst();
    }
}