算法:Sliding Window Maximum
LeetCode地址:https://leetcode.com/problems/sliding-window-maximum/
题目:
Given an array 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:
Input: nums = [1,3,-1,-3,5,3,6,7], and 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
Note:
You may assume k is always valid, 1 ≤ k ≤ input array’s size for non-empty array.
Follow up:
Could you solve it in linear time?
把数组按照个数为k分组,最后一组可能不是全部数据。以组为单位,从左到右方向,逐个计算组内遇到的最大数,记录为新的数组leftMaxArray;再从右到左方向,逐个计算组内遇到的最大数,记录为新的数组rightMaxArray.
因为从左到右方向–>是最大数,从右到左方向<–也是最大数,所以在某个位置index, k个数里面最多只能跨两个域,所以Max(leftMaxArray[index], rightMaxArray[index - k + 1])就是最大数。
可能文字不清晰,直接拿例子数据说明:
Input: nums = [1,3,-1,-3,5,3,6,7], and k = 3
划分数组分块, size k=3. 最有一块个数可能小于k.
1,3,-1 | -3,5,3 | 6,7 |
--> 从左到右方向, 计算分块里遇到的最大数.
leftMaxArray[] = 1,3,3 | -3,5,5 | 6,7
<-- 类似的从右到左计算分块里遇到的最大数.
rightMaxArray[] = 3,3,-1 | 5,5,3 | 7,7
现在, 滑动窗口在位置i, sliding-max(i) = max{rightMaxArray(i), leftMaxArray(i+w-1)}
sliding_max = 3, 3, 5, 5, 6, 7
执行效率:因为是2n-k+1, 所以是O(n).
public class SlidingWindowMaximum {
public int[] maxSlidingWindow(int[] nums, int k) {
if (nums == null || nums.length == 0 || k <= 0) {
return new int[0];
}
int len = nums.length;
int[] windowMaxArray = new int[len - k + 1];
int[] leftMaxArray = new int[len];
int[] rightMaxArray = new int[len];
int rightIndex;
for (int i = 0; i < len; i++) {
leftMaxArray[i] = i % k == 0 ? nums[i] : Math.max(leftMaxArray[i - 1], nums[i]);
rightIndex = len - i - 1;
rightMaxArray[rightIndex] = (i == 0 || (rightIndex + 1) % k == 0) ? nums[rightIndex] : Math.max(rightMaxArray[rightIndex + 1], nums[rightIndex]);
}
for (int j = 0; j <= len - k; j++) {
windowMaxArray[j] = Math.max(leftMaxArray[j + k - 1], rightMaxArray[j]);
}
return windowMaxArray;
}
public static void main(String[] args) {
int[] nums = {1,3,-1,-3,5,3,6,7};
int k = 3;
SlidingWindowMaximum obj = new SlidingWindowMaximum();
int[] windowMax = obj.maxSlidingWindow(nums, k);
System.out.println("Output: " + Arrays.toString(windowMax));
}
}
Output: [3, 3, 5, 5, 6, 7]
用双向链表Deque来存储遍历过的有效数组下标,这里有两个概念,
poll()
方法,一直while循环从头开始清理。如果上一个数据小于当前i,那么也就是无效的数据,不可能在i没剔除之前成为最大数,也会被提前清理掉。那么最大数都在Deque的最前面,所以在for循环的最后一步是存储,最大值。需要注意边界,也就是 i + 1 >= k
开始存结果数据。
nums中的数据最多只会增加一次,清理一次,也就是摊销下来总时间小于2n,时间复杂度为O(n) .
import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.Deque;
public class SlidingWindowMaximum {
public int[] maxSlidingWindowWithDeque(int[] nums, int k) {
if (nums == null || nums.length == 0 || k <= 0) {
return new int[0];
}
int len = nums.length;
int[] windowMax = new int[len - k + 1];
int windowIndex = 0;
// store index
Deque<Integer> deque = new ArrayDeque<>();
for (int i = 0; i < len; i++) {
// remove numbers out of range k
while (!deque.isEmpty() && deque.peek() < i - k + 1) {
deque.poll();
}
// remove smaller numbers in k range as they are useless
while (!deque.isEmpty() && nums[deque.peekLast()] < nums[i]) {
deque.pollLast();
}
// deque contains index... r contains content
deque.offer(i);
if (i + 1 >= k) {
windowMax[windowIndex++] = nums[deque.peek()];
}
}
return windowMax;
}
public static void main(String[] args) {
int[] nums = {1,3,-1,-3,5,3,6,7};
int k = 3;
SlidingWindowMaximum obj = new SlidingWindowMaximum();
int[] windowMax = obj.maxSlidingWindowWithDeque(nums, k);
System.out.println("Output: " + Arrays.toString(windowMax));
}
}
Output: [3, 3, 5, 5, 6, 7]
Runtime: 11 ms, faster than 73.94% of Java online submissions for Sliding Window Maximum.
Memory Usage: 42.7 MB, less than 41.26% of Java online submissions for Sliding Window Maximum.
Sliding Window Maximum(滑动窗口最大值)解决方案:
代码下载:
https://github.com/zgpeace/awesome-java-leetcode/blob/master/code/LeetCode/src/popular/SlidingWindowMaximum.java