数据结构-单调队列优化之队列的最大值
2023-09-01 21:22:29
队列回顾
一个队列,又名先进先出队列,是一种遵循先进先出(FIFO)原则的数据结构。队列中的元素按照它们进入队列的顺序排列。元素只能从队列的头部(front)进入,并且只能从队列的尾部(rear)移除。队列的典型操作包括:
enqueue(item)
:将新元素添加到队列的尾部。dequeue()
:从队列的头部删除并返回元素。size()
:返回队列中元素的数量。isEmpty()
:检查队列是否为空。peek()
:返回队列的头部元素,但不删除它。
队列通常用数组或链表实现。数组实现简单,但可能需要进行昂贵的重新分配操作来处理队列的增长和收缩。链表实现更灵活,但它的性能可能不如数组实现好。
单调队列简介
单调队列是一种特殊的队列,它满足单调性条件,即队列中的元素按某种顺序排列(升序或降序)。单调队列通常用于解决各种数据结构问题,例如求滑动窗口的最大值、最小值等。
如何实现单调队列?
一种简单的方法是使用两个队列来模拟单调队列。一个队列用于存储元素,另一个队列用于存储最大值(或最小值)。当一个新元素进入单调队列时,它被添加到存储元素的队列中。同时,如果新元素比存储最大值(或最小值)的队列中的最大值(或最小值)更大(或更小),则它也被添加到存储最大值(或最小值)的队列中。
当从单调队列中删除一个元素时,它从存储元素的队列中删除。同时,如果被删除的元素是存储最大值(或最小值)的队列中的最大值(或最小值),则它也被从存储最大值(或最小值)的队列中删除。
单调队列的时间复杂度
单调队列的均摊时间复杂度为O(1)。这是因为,在最坏的情况下,单调队列的操作需要O(n)的时间,其中n是队列中的元素数量。然而,在大多数情况下,单调队列的操作只需要O(1)的时间。
用单调队列优化队列的最大值
我们现在来看一个经典的问题:队列的最大值 。
问题
给定一个整数数组nums和一个窗口大小k,您需要找到 nums 所有连续子数组的最大值。
例如,对于 nums = [1, 3, -1, -3, 5, 3, 6, 7] 和 k = 3,您需要找到所有长度为 3 的连续子数组的最大值。结果是 [3, 3, 5, 5, 6, 7]。
解题思路:
这个问题可以用单调队列来优化。具体步骤如下:
- 初始化一个单调队列,并将第一个窗口中的元素加入队列中。
- 从第二个窗口开始,对于每个窗口:
- 将当前窗口的右边界元素加入队列中。
- 如果队列的头部元素不是当前窗口的左边界元素,则将队列的头部元素弹出。
- 将队列的头部元素作为当前窗口的最大值。
- 将所有窗口的最大值输出。
算法复杂度:
该算法的时间复杂度为O(n),其中n是nums数组的长度。这是因为,每个元素最多被加入和弹出队列一次。
Python代码实现:
def maxSlidingWindow(nums, k):
"""
:type nums: List[int]
:type k: int
:rtype: List[int]
"""
if not nums or k <= 0 or len(nums) < k:
return []
# 初始化单调队列和结果列表
queue = []
result = []
# 将第一个窗口中的元素加入队列中
for i in range(k):
while queue and nums[i] > nums[queue[-1]]:
queue.pop()
queue.append(i)
# 从第二个窗口开始,对每个窗口进行处理
for i in range(k, len(nums)):
# 将当前窗口的右边界元素加入队列中
while queue and nums[i] > nums[queue[-1]]:
queue.pop()
queue.append(i)
# 如果队列的头部元素不是当前窗口的左边界元素,则将队列的头部元素弹出
if queue[0] <= i - k:
queue.pop(0)
# 将队列的头部元素作为当前窗口的最大值
result.append(nums[queue[0]])
return result
总结
在本文中,我们介绍了单调队列这一重要的数据结构,并通过队列的最大值问题展示了它的应用。通过使用单调队列,我们可以在O(n)的时间复杂度内解决这一问题。
单调队列的应用非常广泛,除了队列的最大值问题之外,它还可用于解决各种数据结构问题,例如求滑动窗口的最大值、最小值、中位数等。掌握单调队列的使用方法将帮助您解决更多复杂的数据结构问题。