返回
Featured image of post 剑指 Offer 59 - I. 滑动窗口的最大值

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

找出所有滑动窗口中的最大值

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

给定一个数组 nums 和滑动窗口的大小 k,请找出所有滑动窗口里的最大值。

示例 1:

输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3 输出: [3,3,5,5,6,7] 解释:

滑动窗口的位置 最大值


[1 3 -1] -3 5 3 6 7 3 1 [3 -1 -3] 5 3 6 7 3 1 3 [-1 -3 5] 3 6 7 5 1 3 -1 [-3 5 3] 6 7 5 1 3 -1 -3 [5 3 6] 7 6 1 3 -1 -3 5 [3 6 7] 7

提示:

你可以假设 k 总是有效的,在输入数组不为空的情况下,1 ≤ k ≤ 输入数组的大小。


题解一 单调队列

要求滑动窗口内的最大值,首先想到的是双向队列,一边推窗一边保持最大元素在队首。由于要确保窗口长度,队列中存的是下标。

public int[] maxSlidingWindow(int[] nums, int k) {
    int[] res = new int[nums.length - k + 1];
    Deque<Integer> deque = new ArrayDeque<>();
    for (int i = 0; i < nums.length; ++i) {
        if (!deque.isEmpty() && deque.peekFirst() == i - k) {
            deque.removeFirst();
        }
        while (!deque.isEmpty() && nums[deque.peekLast()] < nums[i]) {
            deque.removeLast();
        }
        deque.addLast(i);
        if (i >= k - 1) {
            res[i - k + 1] = nums[deque.peekFirst()];
        }
    }
    return res;
}

题解二 双向遍历

如果通过固定队列长度可以来限制搜索范围,通过双向遍历也可以。不依靠队列的话有个问题,最大值会不断传播,如果 [0] 处是数组最大值,这一最大值可以一直传播到队尾。那我们必须进行适当的划分,让最大值最远传播 k 个数字(包含自己)。

我们直接将队列分成一段段长为 k 的区间,每一段区间中,第一次遍历取得前半段([0.. i % k])上的最大值。

for (int i = 0; i < n; ++i) {
    if (i % k == 0) {
        prefixMax[i] = nums[i];
    }
    else {
        prefixMax[i] = Math.max(prefixMax[i - 1], nums[i]);
    }
}

然后从后向前进行一次遍历,取得后半段([i % k .. k - 1])上的最大值。

for (int i = n - 1; i >= 0; --i) {
    if (i == n - 1 || (i + 1) % k == 0) {
        suffixMax[i] = nums[i];
    } else {
        suffixMax[i] = Math.max(suffixMax[i + 1], nums[i]);
    }
}

然后,我们要取以 i 为起点长度为 k 的窗口的最大值,实际要取的就是当前区间上 [i % k .. k - 1] 上的最大值,和下一区间 [0 .. (i - 1 + k) % k] 上的最大值。

完整答案

public int[] maxSlidingWindow(int[] nums, int k) {
    int n = nums.length;
    if (n == 0) {
        return new int[0];
    }
    int[] prefixMax = new int[n];
    int[] suffixMax = new int[n];
    for (int i = 0; i < n; ++i) {
        if (i % k == 0) {
            prefixMax[i] = nums[i];
        }
        else {
            prefixMax[i] = Math.max(prefixMax[i - 1], nums[i]);
        }
    }
    for (int i = n - 1; i >= 0; --i) {
        if (i == n - 1 || (i + 1) % k == 0) {
            suffixMax[i] = nums[i];
        } else {
            suffixMax[i] = Math.max(suffixMax[i + 1], nums[i]);
        }
    }
    int[] ans = new int[n - k + 1];
    for (int i = 0; i <= n - k; ++i) {
        ans[i] = Math.max(suffixMax[i], prefixMax[i + k - 1]);
    }
    return ans;
}
comments powered by Disqus