一、题目描述
给定一个数组 nums 和滑动窗口的大小 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 ≤ 输入数组的大小。
二、思路分析
注:思路分析中的一些内容和图片参考自力扣各位前辈的题解,感谢他们的无私奉献
思路
设窗口区间为 [ i , j ] [i,j] [i,j],对于每个滑动窗口,我们可以使用 O ( k ) O(k) O(k) 的时间遍历其中的每一个元素,找出其中的最大值。对于长度为 n n n 的数组 n u m s nums nums 而言,窗口的数量为 n − k + 1 n−k+1 n−k+1,因此该算法的时间复杂度为 O ( ( n − k + 1 ) k ) = O ( n k ) O((n-k+1)k)=O(nk) O((n−k+1)k)=O(nk),会超出时间限制,因此我们需要进行一些优化。我们可以想到,对于两个相邻(只差了一个位置)的滑动窗口,它们共用着 k − 1 k-1 k−1 个元素,而只有 1 1 1 个元素是变化的。我们可以根据这个特点进行优化。
本题难点: 如何在每次窗口滑动后,将获取窗口内最大值的时间复杂度从 O ( k ) O(k) O(k) 降低至 O ( 1 ) O(1) O(1)
回忆剑指Offer:[第1天 栈与队列(简单)]—>包含min函数的栈,其使用单调栈实现了随意入栈、出栈情况下的 O ( 1 ) O(1) O(1) 时间获取栈内最小值。本题同理,不同点在于出栈操作删除的是列表尾部元素,而窗口滑动删除的是列表首部元素。可以建立一个单调队列 d e q u e deque deque,遍历数组时,每轮保证单调队列 d e q u e deque deque:
① d e q u e deque deque 内仅包含窗口内的元素 ⇒ \Rightarrow ⇒ 每轮窗口滑动移除了元素 n u m s [ i − 1 ] nums[i - 1] nums[i−1],需将 d e q u e deque deque 内的对应元素一起删除。
② d e q u e deque deque 内的元素非严格递减 ⇒ \Rightarrow ⇒ 每轮窗口滑动添加了元素 n u m s [ j + 1 ] nums[j + 1] nums[j+1],需将 d e q u e deque deque 内所有 < n u m s [ j + 1 ] < nums[j + 1] <nums[j+1] 的元素删除。
算法流程:
①初始化:双端队列 d e q u e deque deque,结果列表 r e s res res,数组长度 n n n
②滑动窗口:左边界范围 i ∈ [ 1 − k , n − k ] i \in [1 - k, n - k] i∈[1−k,n−k],右边界范围 j ∈ [ 0 , n − 1 ] j \in [0, n - 1] j∈[0,n−1]
----若 i > 0 i > 0 i>0 且队首元素 d e q u e [ 0 ] = = deque[0]== deque[0]== 被删除元素 n u m s [ i − 1 ] nums[i - 1] nums[i−1]:则队首元素出队
----删除 d e q u e deque deque 内所有 < n u m s [ j ] < nums[j] <nums[j] 的元素,以保持 d e q u e deque deque 递减
----将 n u m s [ j ] nums[j] nums[j] 添加至 d e q u e deque deque 尾部
----若已形成窗口(即 i ≥ 0 i \geq 0 i≥0):将窗口最大值(即队首元素 d e q u e [ 0 ] deque[0] deque[0])添加至列表 r e s res res
③返回值:返回结果列表 r e s res res
案例分析:
复杂度分析:
时间复杂度 O ( N ) \rm{O(N)} O(N):其中N
为数组nums
长度,线性遍历nums
占用O(N)
。每个元素最多仅入队和出队一次,因此单调队列deque
占用O(2N)
空间复杂度 O ( K ) \rm{O(K)} O(K):双端队列deque
中最多同时存储k
个元素(即窗口大小)
三、整体代码
整体代码如下
/**
* Note: The returned array must be malloced, assume caller calls free().
*/
int *maxSlidingWindow(int *nums, int numsSize, int k, int *returnSize)
{
*returnSize = 0;
if (numsSize == 0) {
return NULL;
}
// 1、使用数组模拟队列
int *queue = (int*)malloc(sizeof(int) * numsSize);
if (queue == NULL) {
return NULL;
}
memset(queue, 0, sizeof(int) * numsSize);
// 2、对前k个元素,模拟双端队列找出第一个最大值
int head = 0, rear = 0;
for (int i = 0; i < k; i++) {
while (head < rear && nums[i] > nums[queue[rear - 1]]) {
rear--;
}
queue[rear++] = i;
}
int* res = (int*)malloc(sizeof(int) * (numsSize - k + 1));
res[(*returnSize)++] = nums[queue[head]];
// 3、滑动窗口,模拟双端队列找出滑动中的最大值
for (int i = k; i < numsSize; i++) {
// 1)窗口向前滑动,移入新数字
while (head < rear && nums[i] > nums[queue[rear - 1]]) {
rear--;
}
queue[rear++] = i;
// 2)移出旧数字
while (queue[head] <= i - k) {
head++;
}
// 3)更新当前窗口的最大值
res[(*returnSize)++] = nums[queue[head]];
}
return res;
}
运行,测试通过