239.滑动窗口最大值
题目链接LeetCode-239
题目
给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。
返回滑动窗口中的最大值。
示例:
输入: 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 ≤ 输入数组的大小。
解法1
- 使用队列保存最大值。
- 依次遍历数组,当前值为
nums[i]
,队尾值为queueLastNum
,nums[i]
和queueLastNum
比较,当nums[i] > queueLastNum
时,queueLastNum
被弹出,nums[i]
和下一个queueLastNum
比较,以此类推,直到nums[i]<=queueLastNum
时或队列为空,num[i]
直接插入到队列中。 - 队列中的值按照在滑动窗口的大小排序,这样保证了,滑动队列一直往后滑动,队头永远为滑动窗口的最大值。
- 如果滑动窗口中移除的值
nums[i-k+1]
为队头,则队列也要移除队头ququeFristNum
,说明滑动窗口的最大值被移除了,队列中下一个值变为队头,即为滑动窗口新的最大值。 - 注意队列要保存下标而不是值,因为有可能下标不同但是值相同,导致队头被错误移除。
模拟
以上面的数组为例,依次遍历数组,保存最大值的队列为maxQueue
,滑动窗口的数量为3
- 1进队列
(1)
- 3 > 1,1被移除,队列为
(3)
- -1 < 3,-1进入队列
(3 -1)
,滑动窗口为[1 3 -1]
- -3 < -1 ,-3队列进入
(3 -1 -3)
,滑动窗口为[3,-1,-3]
- 滑动窗口后移,3被移除,队头也为3,3被移除,队列为
(-1,-3)
,5>-3,-3移除,5>-1,-1移除,队列为(5)
,滑动窗口为[-1 -3 5]
- 3 < 5,3进入队列为
[5 3]
,滑动窗口为(-3,5,3)
- 6 > 3,3被移除,6 > 5,5被移除,队列为
(6)
,滑动窗口为[5 3 6]
- 7 > 6,6被移除,队列为
(7)
,滑动窗口为[3 6 7]
- 至此,我们每次循环数组判断,满足滑动窗口的条件,将队头的值作为最大值,保存到结果数组中。当遍历完成,结果数组保存的值也是每个滑动窗口的最大值。
时间复杂度分析
不要以为for
里面套了一个while
就是O(
n
2
n^2
n2),因为队列是动态移除的,并不是固定n个值,极端情况,就是n个值,前n-1个值,都没进入while
循环,最后一次,移除队列中所有的值为n,循环了n次。
把n次执行时间均摊到n-1操作,即每次操作都为O(1),时间复杂度即为O(n)。
代码
public int[] maxSlidingWindow(int[] nums, int k) {
if (nums == null || nums.length == 0) {
return new int[0];
}
LinkedList<Integer> maxQueue = new LinkedList<>();
int[] maxArray = new int[nums.length - k + 1];
for (int i = 0; i < nums.length; i++) {
// 如果移除的值和maxQueue中保存值相同
if (maxQueue.peek()!=null && i-k == maxQueue.peekFirst()) {
maxQueue.pollFirst();
}
// 如果队列不为空且当前值大于队尾,依次移除
while (!maxQueue.isEmpty() && nums[i] > nums[maxQueue.getLast()]) {
maxQueue.pollLast();
}
maxQueue.offer(i);
if (maxQueue.peekFirst() != null && i >= k-1) {
maxArray[i - k + 1] = nums[maxQueue.peekFirst()];
}
}
return maxArray;
}