【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 ≤ 输入数组的大小。

进阶:
你能在线性时间复杂度内解决此题吗?

解答

方法一:暴力破解。cur表示窗口,记录最大值,依次后移。时间复杂度O(n·k),timeout

方法二:大顶堆。Time: O(N*logN), Space: O(N),timeout
思路:元素和下标依次入堆,每次获取窗口最大值时,检查堆顶元素的下标在不在窗口内,不在就pop掉,不断地移除堆顶的元素,直到其确实出现在滑动窗口中。此时,堆顶元素就是滑动窗口中的最大值。
PS:把元素组合(x, y)入堆,可以保持堆特性。

方法三:双端队列,时间复杂度O(n), 空间复杂度O(k)
思路:依次遍历列表中的元素,cur存放已经在窗口中的元素的下标,假如当前元素为c, 则用c和cur中元素从后向前比较,删除cur中小于c的元素,并把c的下标加到cur中,这步操作保证了cur[0]表示的元素永远是窗口中最大的

通过代码如下:


# 1. 爆破 timeout Time: O(N*K), Space: O(1)
# 2. 用大顶堆  Time: O(N*logN), Space: O(N)
# 3. 双端队列  Time: O(N), Space: O(K)


# class Solution:
#     # 爆破
#     def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:

#         if not nums: return []

#         ans = []
#         length = len(nums)
#         for i in range(length-k+1):
#             ans.append(max(nums[i: i+k]))
#         return ans


# class Solution:
#     # 大顶堆
#     from heapq import heappush, heappop, heapify
#     def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:

#         if not nums: return []

#         length = len(nums)
#         q = [(-nums[i], i) for i in range(k)]  # 下标也入堆,用于判断元素在不在窗口内
#         heapify(q)

#         ans = [-q[0][0]]
#         for i in range(k, length):
#             heappush(q, (-nums[i], i))
#             while q[0][1] <= i-k:  # 堆顶在不在窗口,不在则删掉
#                 heappop(q)  # log(n)
#             ans.append(-q[0][0])
#         return ans


from collections import deque


class Solution:
    # 双端队列
    def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:

        if not nums: return []

        ans = []
        win = deque([])  # 存数值下标     
        for i, x in enumerate(nums):
            
            # 窗口满了,删除最左侧。(win保存下标,不然删除最左侧 没法删,用下标和win第一个做比较)
            if win and i - win[0] >= k:
                win.popleft()

            # 进窗口之前,删除window小于x的,(x在 他们没有出头之日)
            while win and x >= nums[win[-1]]:
                win.pop()

            # x加入窗口
            win.append(i)

            # 保存window max
            if i+1 >= k:
                ans.append(nums[win[0]])

        return ans
posted @ 2019-12-05 18:43  961897  阅读(302)  评论(0编辑  收藏  举报