题目描述
只用「栈」的 push / pop / 看顶 操作,实现一个队列(先进先出 FIFO)。
思路解析
栈只能从顶进出,队头却在栈底。直觉是「每次 pop 就把整个栈倒到另一个栈、取出底部、再倒回来」,能对,但每次操作都倒两趟,n 次操作就是 O(n²)。难点是怎么让"反序"只做一次、别反复倒。
转折:入队都压进 In;出队时如果 Out 空了,才把 In 一个个倒进 Out——倒完顺序正好反过来,Out 的栈顶就是最先进的队头。关键是只在 Out 空时才倒,倒完的元素在 Out 里慢慢发,发空了再倒下一批。这样每个元素一生只被倒一次。
push 1:push 1:入队元素一律压进 In。现在 In=[1],Out 还空。
push 2:push 2:继续压进 In,In=[1, 2]。In 顶是 2,可队头该是先进的 1——1 现在压在底下取不到,等出队时再处理。
pop · Out 空 → 要倒栈:pop:先检查 Out。Out 空,没现成的队头可发,于是触发倒栈——把 In 里的元素一个个弹出、压进 Out。
倒栈 · 弹 2 压入 Out:倒栈第一下:弹出 In 顶的 2,压进 Out。In=[1],Out=[2]。
倒栈 · 弹 1 压入 Out:倒栈第二下:弹出 1 压进 Out。In 空了,Out=[2, 1]。顺序反转——Out 顶现在正是最先进的 1。
pop · 弹出 Out 顶 1:倒完后从 Out 弹出顶 1——这就是队列该先出的元素,实现了先进先出。Out 还剩 [2]。
push 3 · 进 In 不倒:push 3:新元素照例只进 In,In=[3]。注意此时 Out 里还有没发完的 2,千万别去倒 In——否则 3 会被插到 2 前面,顺序就乱了。In、Out 各管各。
pop · Out 非空 → 不倒,直接弹:pop:先看 Out——这次 Out 非空(有 2)!直接弹出 2,不倒栈。这正是负负得正的省钱之处:只有 Out 空时才倒,非空就接着发。出队顺序 1、2 正确。
栈倒一次反序,进了 Out 再发就相当于把队头放到了顶。只要 Out 没发完就别再倒——这是延迟、摊还的思想。用队列实现栈是同款镜像。
参考代码(Python)
1class MyQueue:
2 def __init__(self): self.In, self.Out = [], []
3 def push(self, x): self.In.append(x) # 入队都进 In
4 def pop(self):
5 self.peek() # 确保 Out 有货
6 return self.Out.pop()
7 def peek(self):
8 if not self.Out: # 只在 Out 空时才倒
9 while self.In: # 把 In 整体倒进 Out
10 self.Out.append(self.In.pop())
11 return self.Out[-1]复杂度分析
- 时间复杂度:均摊 O(1) —— 单次倒栈是 O(n),但每个元素一生只被倒一次,n 次操作平摊到每次就是 O(1)
- 空间复杂度:O(n) —— In 和 Out 加起来最多同时存 n 个元素
套路模板
记住骨架:In 只管收、Out 空了才把 In 整体倒过来、每个元素一生只倒一次(均摊 O(1))。用队列实现栈是镜像版。
1# 用两个栈模拟先进先出 / 摊还反序都套
2In, Out = [], []
3def push(x): In.append(x) # 进都进 In
4def pop():
5 if not Out: # 只在 Out 空时才倒
6 while In: Out.append(In.pop()) # 一次倒完 = 反序
7 return Out.pop()易错点
- 错误写法:Out 非空时还去倒 In → 正确写法:先判 if not Out,非空就直接弹(像 push 1,2 → pop 后 Out=[2],此时再 push 3 又倒 In,会把 3 插到 2 前面,出队顺序就乱成 3、2)
- 错误写法:每次出队都把 Out 全倒回 In 再倒过去 → 正确写法:只在 Out 为空时才倒一次(反复来回倒既打乱顺序、又把均摊 O(1) 退化成 O(n))
以上文字与上方动画完全同一思路:先看动画建立直觉 → 读文字巩固 → 关掉页面自己默写一遍代码,能写出来才算真的掌握。