在数据结构的世界里,栈(Stack)和队列(Queue)就像是一对性格迥异的双胞胎。栈遵循LIFO(Last In First Out)原则,就像我们叠放的一摞盘子,最后放上去的总是最先被取用;而队列则遵循FIFO(First In First Out)原则,更像是排队买奶茶的场景,先来的人先获得服务。
这个题目之所以经典,是因为它要求我们用栈这种"后进先出"的结构,模拟出队列"先进先出"的行为。这就好比要用一个只能从顶部放入和取出物品的箱子,模拟出两端都能操作的管道——看似矛盾的需求背后,其实蕴含着精妙的设计思路。
实现这个"魔法"的关键在于使用两个栈——我们暂且称它们为输入栈(inputStack)和输出栈(outputStack)。当需要执行队列的push操作时,我们直接将元素压入输入栈;当需要执行pop或peek操作时,如果输出栈为空,我们就把输入栈的所有元素依次弹出并压入输出栈,这样原本在输入栈底的元素就到了输出栈顶。
这个过程就像把一摞书从左手倒到右手——原本在最下面的书现在跑到了最上面。通过这种巧妙的"倒腾",我们实现了元素的顺序反转,从而用栈模拟出了队列的行为。
这种设计的美妙之处在于它的均摊时间复杂度:
python复制class MyQueue:
def __init__(self):
self.input_stack = []
self.output_stack = []
我们首先定义MyQueue类,初始化两个空栈。这里使用Python的list来模拟栈的行为,因为list的append和pop操作正好对应栈的push和pop。
python复制def push(self, x: int) -> None:
self.input_stack.append(x)
push操作极其简单——直接将新元素追加到input_stack的末尾。这就是标准的栈push操作,时间复杂度O(1)。
python复制def pop(self) -> int:
if not self.output_stack:
while self.input_stack:
self.output_stack.append(self.input_stack.pop())
return self.output_stack.pop()
pop操作稍微复杂些:
关键点:只有当output_stack为空时,才需要从input_stack"倒货",这样可以确保每个元素只被移动两次,保证均摊时间复杂度。
python复制def peek(self) -> int:
if not self.output_stack:
while self.input_stack:
self.output_stack.append(self.input_stack.pop())
return self.output_stack[-1]
peek操作与pop类似,只是不实际移除元素。同样需要先确保output_stack不为空,然后返回栈顶元素而不弹出。
python复制def empty(self) -> bool:
return not self.input_stack and not self.output_stack
判断队列是否为空很简单——只需检查两个栈是否都为空即可。
在实际编码中,我们需要特别注意几个边界情况:
虽然双栈法已经相当高效,但我们还可以思考一些优化方向:
这种栈模拟队列的技术在实际中有许多妙用:
在实现过程中,新手常会遇到这些问题:
调试时可以:
作为反向思考,LeetCode也有用队列实现栈的题目(225题)。这两种题目常常成对出现,建议对比学习。用队列实现栈的核心思路是:
这种实现方式push是O(n),pop是O(1),正好与栈模拟队列的情况形成有趣对比。