目录
设计一个支持前、中、后三个位置插入和删除的队列 — FrontMiddleBack 队列实现详解
设计一个支持前、中、后三个位置插入和删除的队列 — FrontMiddleBack 队列实现详解
题目描述
设计一个特殊的队列数据结构,支持在队列的 前、中、后 三个位置进行元素的插入和删除操作。具体要求如下:
实现一个类 FrontMiddleBack
,包含以下方法:
FrontMiddleBack()
:初始化队列。void pushFront(int val)
:将元素val
添加到队列最前面。void pushMiddle(int val)
:将元素val
添加到队列的正中间。void pushBack(int val)
:将元素val
添加到队列最后面。int popFront()
:删除并返回队列最前面的元素,如果队列为空则返回-1
。int popMiddle()
:删除并返回队列中间的元素,如果队列为空则返回-1
。如果有两个中间元素,则删除靠前面的那个。int popBack()
:删除并返回队列最后面的元素,如果队列为空则返回-1
。
中间位置的定义
- 若队列长度为奇数,中间位置就是正中间的那个元素。
- 若队列长度为偶数,中间位置有两个元素,此时中间位置指的是靠前的那个元素。
例如:
- 队列
[1, 2, 3, 4, 5]
,中间位置是元素3
。 - 队列
[1, 2, 3, 4, 5, 6]
,中间位置是元素3
(靠前那个)。
示例:
- 向
[1, 2, 3, 4, 5]
中间插入6
,结果为[1, 2, 6, 3, 4, 5]
。 - 从
[1, 2, 3, 4, 5, 6]
弹出中间元素,弹出3
,结果为[1, 2, 4, 5, 6]
。
解题分析
实现一个支持三处插入和删除的队列,关键在于:
- 插入和删除中间元素 的效率问题。如果使用普通列表或数组,每次中间插入或删除都可能导致 O(n) 的移动开销,效率较低。
- 如何快速定位中间元素,以及在保持中间位置定义的同时,维持队列元素的顺序。
要实现所有操作的高效性(理想情况是均摊 O(1) 或接近 O(1)),我们需要采用更巧妙的数据结构。
解题方法
方案:使用两个双端队列(deque
)
思路是将整个队列拆分成两个部分:
- 左半部分(
left
):存储队列前半部分元素 - 右半部分(
right
):存储队列后半部分元素
我们确保:
left
的元素个数 ≥right
的元素个数,且两者长度差不超过 1。- 这样,队列的“中间”元素就始终是
left
的最后一个元素。
操作实现细节:
- pushFront(val):
-
- 将
val
插入到left
队列头部。 - 之后重新平衡两个队列,保证长度条件。
- 将
- pushMiddle(val):
-
- 如果
left
比right
长,将left
的末尾元素移动到right
的头部。 - 将
val
添加到left
的末尾。 - 这样保证新元素插入在靠前的中间位置。
- 如果
- pushBack(val):
-
- 将
val
添加到right
的末尾。 - 重新平衡。
- 将
- popFront():
-
- 如果两个队列都为空,返回 -1。
- 优先从
left
的头部弹出元素;如果left
空,从right
头部弹出。 - 重新平衡。
- popMiddle():
-
- 如果队列为空,返回 -1。
- 弹出
left
的末尾元素。 - 重新平衡。
- popBack():
-
- 如果队列为空,返回 -1。
- 优先从
right
末尾弹出;如果right
为空,从left
末尾弹出。 - 重新平衡。
维护平衡的关键函数 _rebalance()
:
- 如果
left
比right
长超过 1 个元素,将left
的末尾元素移动到right
的头部。 - 如果
right
比left
长,将right
的头部元素移动到left
的末尾。
这样保证了:
left
始终保持元素数量 ≥right
- 差值不超过 1
代码实现(Python)
from collections import deque
class FrontMiddleBackQueue:
def __init__(self):
self.left = deque()
self.right = deque()
def _rebalance(self):
# 保证left的大小 >= right,且差值不超过1
while len(self.left) > len(self.right) + 1:
self.right.appendleft(self.left.pop())
while len(self.left) < len(self.right):
self.left.append(self.right.popleft())
def pushFront(self, val: int) -> None:
self.left.appendleft(val)
self._rebalance()
def pushMiddle(self, val: int) -> None:
if len(self.left) > len(self.right):
self.right.appendleft(self.left.pop())
self.left.append(val)
def pushBack(self, val: int) -> None:
self.right.append(val)
self._rebalance()
def popFront(self) -> int:
if not self.left and not self.right:
return -1
val = self.left.popleft() if self.left else self.right.popleft()
self._rebalance()
return val
def popMiddle(self) -> int:
if not self.left and not self.right:
return -1
val = self.left.pop()
self._rebalance()
return val
def popBack(self) -> int:
if not self.left and not self.right:
return -1
val = self.right.pop() if self.right else self.left.pop()
self._rebalance()
return val
复杂度分析
- 时间复杂度:
-
- 所有操作的均摊时间复杂度均为 O(1)。
由于 _rebalance()
最多每次移动一个元素,且 deque
的 append
/pop
/appendleft
/popleft
都是 O(1),所以操作高效。
- 空间复杂度:
-
- 使用两个
deque
,总空间是元素数目的两倍,空间复杂度为 O(n)。
- 使用两个
示例说明
q = FrontMiddleBackQueue()
q.pushFront(1) # [1]
q.pushBack(2) # [1, 2]
q.pushMiddle(3) # [1, 3, 2]
q.pushMiddle(4) # [1, 4, 3, 2]
print(q.popFront()) # 返回 1,队列变成 [4, 3, 2]
print(q.popMiddle())# 返回 4,队列变成 [3, 2]
print(q.popBack()) # 返回 2,队列变成 [3]
print(q.popFront()) # 返回 3,队列变成 []
print(q.popFront()) # 返回 -1,队列为空
通过以上示例,可以看到:
pushMiddle
在两个中间元素时,插入靠前的位置。popMiddle
删除靠前的中间元素。- 其他操作正常维护顺序。
其他实现思路与对比
- 单链表或数组实现:
-
- 直接插入或删除中间元素需要移动大量元素,导致操作 O(n)。
- 平衡树或跳表:
-
- 可实现 O(log n) 插入和删除,但实现复杂度较高。
- 两个双端队列(本方案):
-
- 实现简单且操作效率高,适合此题。
总结
通过巧妙使用两个双端队列,并在每次操作后保持平衡,我们成功实现了一个支持在 前、中、后 三个位置插入和删除元素的队列结构,且保证了所有操作的高效执行。
这个设计既优雅又实用,是数据结构设计中的经典案例,值得学习和掌握。
如果你对该题目实现还有其他疑问或想用其他语言版本实现,欢迎随时告诉我!