剑指Offer:[第27天 栈与队列(困难)]--->滑动窗口的最大值


一、题目描述

给定一个数组 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 nk+1,因此该算法的时间复杂度为 O ( ( n − k + 1 ) k ) = O ( n k ) O((n-k+1)k)=O(nk) O((nk+1)k)=O(nk),会超出时间限制,因此我们需要进行一些优化。我们可以想到,对于两个相邻(只差了一个位置)的滑动窗口,它们共用着 k − 1 k-1 k1 个元素,而只有 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[i1],需将 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[1k,nk],右边界范围 j ∈ [ 0 , n − 1 ] j \in [0, n - 1] j[0,n1]
----若 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[i1]:则队首元素出队
----删除 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 i0):将窗口最大值(即队首元素 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;
}

运行,测试通过
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

知初与修一

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值