持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第11天,点击查看活动详情
给你一个整数数组nums
,有一个大小为k
的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的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
示例 2:
输入:nums = [1], k = 1
输出:[1]
提示:
1 <= nums.length <= 105
-104 <= nums[i] <= 104
1 <= k <= nums.length
解题思路:单调队列
在开始前,先复习下什么是双端数列
我们应该都学过简单的队列和栈吧
- 普通数列是限制仅在队尾可进行插入,在队头可进行删除操作的线性表,队列的插入叫做入队列,队列的删除叫做出队列
- 栈则是先进后出,后进先出,和队列正好相反
双端队列可以理解为是两种数据结构的合体,它同时具备了队列和栈的特性。即队列的队头既可以进行插入操作,也可以进行删除操作。队列的队尾也既可以进行插入操作,又可以进行删除操作。
而有些时候,双端队列还有受限的双端队列:即输入受限的双端队列,和输出受限的双端队列
-
输入受限的双端队列:允许在一段进行入队和出队,但在另一端只允许出队的双端队列。
-
输入受限的双端队列:允许在一段进行入队和出队,但在另一端只允许入队的双端队列。
而输出受限的双端队列里,又有一种情况,即队列里的各元素之间的关系具有单调性,这叫单调队列。
单调队列:所有队列里的元素都是按递增(递减) 的顺序队列,这个队列的头是最小(最大)的元素。
这道题,用到的就是双端队列中的单调队列
具体思路:
- 因为这是一个滑动窗口,我们需要不断的向右移动,所以窗口里的值是在不断变化的。向右移动的过程中左边的元素不断被删除,右边的元素不断添加进来,我们只需要在这个窗口里寻找最大值
- 那我们就可以只在队列中保留可能成为窗口最大元素的元素,去掉不可能成为窗口中最大元素的元素。想象一下,如果要进来的是个值大的元素,那一定会比之前早进去的值小的元素晚离开队列,而且值大的元素在,都没值小的元素啥事,所以值小的元素直接弹出队列即可。
- 遍历给定数组中的元素,如果队列不为空且当前考察元素大于等于队尾元素,则将队尾元素移除。直到,队列为空或当前考察元素小于新的队尾元素;
- 当队首元素的下标小于滑动窗口左侧边界
left
时,表示队首元素已经不再滑动窗口内,因此将其从队首移除。 - 由于数组下标从
0
开始,因此当窗口右边界right+1
大于等于窗口大小k
时,意味着窗口形成。此时,队首元素就是该窗口内的最大值。
具体代码:(JAVA实现)
public static int[] maxSlidingWindow(String[] nums, int k) {
int[] ints = new int[nums.length - k + 1];
LinkedList<Integer> queue = new LinkedList<>();
for (int i = 0; i < nums.length; i++) {
/**
* 保证数列都是从大到小顺序,如果队列前面数字较小,依次弹出,直到队列头部元素为最大值
*/
while (!queue.isEmpty() && Integer.valueOf(nums[queue.peekLast()]) <= Integer.valueOf(nums[i])) {
queue.removeLast();
}
/**
* 添加当下值对应的下标
*/
queue.offer(i);
/**
* 如果当前队列的最左端存的下标等于 i - k 的值,就代表队列满了
* 但是新元素需要进来,所以队列最左端退队列
*/
if (queue.peek() <= i - k) {
queue.poll();
}
/**
* 当前的大值加入到结果数组中
*/
if (i >= k - 1) {
ints[i + 1 - k] = Integer.valueOf(nums[queue.peek()]);
}
}
return ints;
}
复杂度分析
- 时间复杂度:O(n)
- 空间复杂度:O(k),k为辅助队列的长度