算法:滑动窗口的两种精妙解法239. Sliding Window Maximum

Posted 架构师易筋

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了算法:滑动窗口的两种精妙解法239. Sliding Window Maximum相关的知识,希望对你有一定的参考价值。

239. Sliding Window Maximum

You are given an array of integers nums, there is a sliding window of size k which is moving from the very left of the array to the very right. You can only see the k numbers in the window. Each time the sliding window moves right by one position.

Return the max sliding window.

Example 1:

Input: nums = [1,3,-1,-3,5,3,6,7], k = 3
Output: [3,3,5,5,6,7]
Explanation: 
Window position                Max
---------------               -----
[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

Example 2:

Input: nums = [1], k = 1
Output: [1]

Example 3:

Input: nums = [1,-1], k = 1
Output: [1,-1]

Example 4:

Input: nums = [9,11], k = 2
Output: [11]
Example 5:

Input: nums = [4,-2], k = 2
Output: [4]

Constraints:

1 <= nums.length <= 105
-104 <= nums[i] <= 104
1 <= k <= nums.length

1. 用双端队列Deque解法

这里双端队列存的是数组的索引 index。
我们从 0 到 n-1 扫描数组,在双端队列中保留“有希望的”元素。该算法分摊 O(n),因为每个元素都被放置和轮询一次。

在每个 i 处,我们保留“有希望的”元素,这些元素可能是窗口 [i-(k-1),i] 或任何后续窗口中的最大数量。这意味着

  1. 如果双端队列中的元素在 i-(k-1) 之外,则丢弃它们。我们只需要从头部进行轮询,因为我们使用的是双端队列,并且元素按照数组中的序列进行排序

  2. 现在只有 [i-(k-1),i] 中的那些元素在双端队列中。然后我们从尾部丢弃小于 a[i] 的元素。这是因为如果 a[x] <a[i] 和 x<i,那么 a[x] 没有机会成为 [i-(k-1),i] 或任何其他后续窗口中的“最大值” : a[i] 永远是更好的候选人。

  3. 因此,双端队列中的元素按数组及其值的顺序排列。在每一步,双端队列的头部是 [i-(k-1),i] 中的最大元素

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        if (k == 1) return nums;
        int n = nums.length;
        int[] r = new int[n - k + 1];
        int ri = 0;
        Deque<Integer> q = new ArrayDeque<>();
        for (int i = 0; i < n; i++) {
            while (!q.isEmpty() && q.peek() < i - k + 1) {
                q.poll();
            }
            while (!q.isEmpty() && nums[q.peekLast()] < nums[i]) {
                q.pollLast();
            }
            q.offer(i);
            if (i >= k - 1) {
                r[ri++] = nums[q.peek()];
            }
        }
        
        return r;
    }
}

2. 用前序和后续两个数组解法

例如:A = [2,1,3,4,6,3,8,9,10,12,56], w=4

  1. 将数组划分为大小为 w=4 的块。最后一个块可能少于 w。
    2, 1, 3, 4 | 6, 3, 8, 9 | 10, 12, 56|

  2. 从头到尾遍历列表并计算max_so_far。在每个块边界(w 个元素​​)后重置 max。
    left_max[] = 2, 2, 3, 4 | 6, 6, 8, 9 | 10、12、56

  3. 类似地通过从头到尾遍历来计算未来的最大值。
    right_max[] = 4, 4, 4, 4 | 9, 9, 9, 9 | 56, 56, 56

  4. 现在,在当前窗口中的每个位置 i 滑动 max,sliding-max(i) = max{right_max(i), left_max(i+w-1)}
    slide_max = 4, 6, 6, 8, 9, 10, 12 , 56

 public static int[] slidingWindowMax(final int[] in, final int w) {
    final int[] max_left = new int[in.length];
    final int[] max_right = new int[in.length];

    max_left[0] = in[0];
    max_right[in.length - 1] = in[in.length - 1];

    for (int i = 1; i < in.length; i++) {
        max_left[i] = (i % w == 0) ? in[i] : Math.max(max_left[i - 1], in[i]);

        final int j = in.length - i - 1;
        max_right[j] = (j % w == 0) ? in[j] : Math.max(max_right[j + 1], in[j]);
    }

    final int[] sliding_max = new int[in.length - w + 1];
    for (int i = 0, j = 0; i + w <= in.length; i++) {
        sliding_max[j++] = Math.max(max_right[i], max_left[i + w - 1]);
    }

    return sliding_max;
}

证明
假设数组是a0,a1,a2… an窗口宽度是w

我们的目标是获得最大数组d[]。数组有长度n-w+1
我们将整个数组从左边缘划分为窗口,并以 : 的形式表示每个元素i*w+j,i是从左偏移 0 的窗口索引,是窗口j内的偏移量。0<=i<=n/w和0<=j<w

这就是我们想要构建的:
d[iw+j]=max(a[iw+j+x] where 0<=x<w)所以iw+j实际上代表了我们从中计算最大值的窗口。

假设我们有以下数组,(这就是 dp 正在做的)
left[iw+j] = left_max(a[iw+k] where 0<=k<j)注意排他性大于。
right[iw+j]= right_max(a[iw+k] where j<=k<=(i+1)*w-1)
数组left[]是每个窗口从左到右的累积最大值
数组right[]是每个窗口从右到左的累积最大值

我们有等式:
d[iw+j]=max(right[iw+j], left[(i+1)w+j-1])
d[iw+j]=max(right[iw+j], left[(iw+w+j-1])
=>
d[m] = max(right[m], left[m+w-1])

的最后一个元素d[]是d[n-w+1]=max(right[n-w+1], left[n-1])

参考

https://leetcode.com/problems/sliding-window-maximum/discuss/65884/Java-O(n)-solution-using-deque-with-explanation

https://leetcode.com/problems/sliding-window-maximum/discuss/65881/O(n)-solution-in-Java-with-two-simple-pass-in-the-array

以上是关于算法:滑动窗口的两种精妙解法239. Sliding Window Maximum的主要内容,如果未能解决你的问题,请参考以下文章

LeetCode 239. 滑动窗口最大值(最大值的重新利用,Java)

LeetCode 239. 滑动窗口最大值(最大值的重新利用,Java)

LeetCode(算法)- 239. 滑动窗口最大值

LeetCode(算法)- 239. 滑动窗口最大值

代码随想录算法训练营第13天 | ● 239. 滑动窗口最大值 ● 347.前 K 个高频元素 ● 总结

⭐算法入门⭐《队列 - 单调队列》困难01 —— LeetCode 239. 滑动窗口最大值