LeetCode 239. Sliding Window Maximum (经典双端队列dequeue题目,附加动态规划解法)

简介

题目链接 LeetCode 239. Sliding Window Maximum

本题的标签是HeapSliding WindowDequeue,是滑动窗口和双端队列结合的一道经典题。本文将着重讲解双端队列解法,同时对动态规划的解法进行简单的讲解。

注意:文章中一切注解皆为Python代码

理解题目

题目非常简单。给定一个数列,和一个窗口,在窗口从左向右滑动的时候,持续计算窗口中子数列的最大值。

最暴力的解法就是在窗口滑动的过程中持续计算最大值,代码就一行,如下

class Solution:
    def maxSlidingWindow(self, nums: 'List[int]', k: 'int') -> 'List[int]':
        return [max(nums[i-k:i]) for i in range(k, len(nums)+1)]

很明显,暴力解法需要的时间复杂度是O(NK),其中N是数据规模,K是窗口大小。如果窗口大小K等于N/2的话,时间复杂度可以表示为O(N^2)。对于题目中输入范围为105 这种级别,显然O(N^2)这种时间复杂度是不行的,因此需要探索其他解法。

题目剖析+思考过程

想到更优的解法其实并不难,首先我们要意识到这题的两个操作,一是遇到新数字时对当前最大值的更新,二是淘汰过期数字时对当前最大值的更新。

对于第一种情况,我们都知道一个前缀数组(prefix array)就可以解决。那么如何去淘汰过期的数字,并更新最大值呢?显然单纯的前缀数组不好使了,怎么办:

  • 首先,提到“过期”可能最先想到的就是队列(queue),先进先出
  • 其次,淘汰“过期”数字后,需要选出第二大的数字,依次类推,可能还需要第三、第四、第K大的数字

那什么样的数据结构可以高效的提供这样(内容不断变化且能保持一定顺序)的信息呢?

  • 堆(heap)就可以
  • 单调递减的双端队列也可以

这里的堆(heap)很好理解,堆的加入和删除都仅仅耗费O(logK),并且,它时时刻刻能保证顺序;最终的时间复杂度可以近似为O(NlogN)

那么如何去理解单调递减的双端队列呢?

单调递减双端队列

双端队列本身不用多讲,先进先出,两端都可以以O(1)的时间加入和删除;对于单调递减,我们事实上是为了维护一种单调递减的顺序,也就是之前我提到的

淘汰“过期”数字后,需要选出第二大的数字,依次类推,可能还需要第三、第四、第K大的数字

这本质上是一种单调递减的状态,看个例子:

给出数列[9,5,7,6,11],窗口长度为3,起初单调递减队列dq为空,指针i = 0

  • i = 0, dq = [9], 当前max = 9, 窗口中数列为[9], 滑动最大值还未开始计算,因为窗口内数组不满3个
  • i = 1, dq = [9, 5], 当前max = 9, 窗口中数列为[9, 5], 滑动最大值还未开始计算,因为窗口内数组不满3个
  • i = 2, dq = [9, 7], 当前max = 9, 窗口中数列为[9, 5, 7], 滑动最大值[9]
    • 注意为了保证队列的单调递减性,之前的5被弹出来,7加了进去
    • 因为只要7还有没有过期,7肯定比5大
    • 而且7在5之后,肯定比5要更晚过期
  • i = 3, dq = [7,
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值