算法:滑动窗口的两种精妙解法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] 或任何后续窗口中的最大数量。这意味着
-
如果双端队列中的元素在 i-(k-1) 之外,则丢弃它们。我们只需要从头部进行轮询,因为我们使用的是双端队列,并且元素按照数组中的序列进行排序
-
现在只有 [i-(k-1),i] 中的那些元素在双端队列中。然后我们从尾部丢弃小于 a[i] 的元素。这是因为如果 a[x] <a[i] 和 x<i,那么 a[x] 没有机会成为 [i-(k-1),i] 或任何其他后续窗口中的“最大值” : a[i] 永远是更好的候选人。
-
因此,双端队列中的元素按数组及其值的顺序排列。在每一步,双端队列的头部是 [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
-
将数组划分为大小为 w=4 的块。最后一个块可能少于 w。
2, 1, 3, 4 | 6, 3, 8, 9 | 10, 12, 56| -
从头到尾遍历列表并计算max_so_far。在每个块边界(w 个元素)后重置 max。
left_max[] = 2, 2, 3, 4 | 6, 6, 8, 9 | 10、12、56 -
类似地通过从头到尾遍历来计算未来的最大值。
right_max[] = 4, 4, 4, 4 | 9, 9, 9, 9 | 56, 56, 56 -
现在,在当前窗口中的每个位置 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)