232. 用栈实现队列(Implement Queue using Stacks)

难度:简单 | 主题:栈、队列、设计

题目

请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作:pushpeekpopempty

示例:

 
输入:["MyQueue","push","push","peek","pop","empty"] [[],[1],[2],[],[],[]]
 
输出:[null,null,null,1,1,false]
 

思路

先讲个故事:两摞盘子,一个洗碗机

食堂后厨有两个操作台。一边是脏盘子摞(输入栈),一边是干净盘子摞(输出栈)。

脏盘子摞:盘子一个一个往上叠,叠在最上面的最晚来。

洗碗机:把整个脏盘子摞一次性倒到干净盘子那边——顺序就反了

原来最下面的盘子(最早来的)现在到了干净盘子摞的最上面。拿出来用的时候,最早来的最先被拿走——先进先出,完美队列。


引导式推导:负负得正

核心洞察:两次反转 = 原顺序。

  • 栈是 LIFO:1, 2, 3 入栈,弹出是 3, 2, 1

  • 如果再把 3, 2, 1 压入另一个栈,弹出是 1, 2, 3

数据流示意

 
push 1, 2, 3:
 
  输入栈(脏盘子): [1, 2, 3]  ← 栈顶是 3
 
pop():
 
  输入栈全部倒入输出栈 → 输出栈: [3, 2, 1]  ← 栈顶是 1
 
  弹出 1 → 输出栈: [3, 2]  ← 栈顶是 2
 

graph LR

    subgraph SGpv8c6["输入栈(脏盘子)"]

        A["底部"] --> B["1"]

        B --> C["2"]

        C --> D["3(顶部)"]

    end

    subgraph 翻转

        D --> E["弹出3<br/>压入输出栈"]

        C --> F["弹出2<br/>压入输出栈"]

        B --> G["弹出1<br/>压入输出栈"]

    end

    subgraph SG4cc93["输出栈(干净盘子)"]

        G --> H["1(顶部)"]

        F --> I["2"]

        E --> J["3"]

    end

均摊 O(1) 的关键:不是每次 pop 都转移,而是输出栈为空时才转移

每个元素最多经历:入输入栈一次 → 出输入栈一次 → 入输出栈一次 → 出输出栈一次 = 4 次操作。n 次操作总时间 O(n),平均每次 O(1)。


推荐写法

 
class MyQueue:
 
    def __init__(self):
 
        self.in_stack = []   # 输入栈:接收 push 操作
 
        self.out_stack = []  # 输出栈:提供 pop/peek 操作
 
    def push(self, x):
 
        self.in_stack.append(x)  # 直接压入输入栈
 
    def pop(self):
 
        self._transfer()              # 输出栈空时才转移
 
        return self.out_stack.pop()   # 输出栈顶即队首
 
    def peek(self):
 
        self._transfer()              # 同样需要确保输出栈有数据
 
        return self.out_stack[-1]     # 输出栈顶即队首(不弹出)
 
    def empty(self):
 
        return not self.in_stack and not self.out_stack  # 两个栈都空才空
 
    def _transfer(self):
 
        if not self.out_stack:                    # 关键:只在输出栈空时转移
 
            while self.in_stack:                  # 倒空整个输入栈
 
                self.out_stack.append(self.in_stack.pop())
 

复杂度

指标解释
pushO(1)直接入栈
pop/peek均摊 O(1)每个元素最多转移一次
emptyO(1)检查两个栈
空间O(n)两个栈合计存 n 个元素

实战考量

频率分析

出现在:字节/微软/亚麻 OA 高频,设计轮热身题。约 20% 的设计面会从”实现数据结构”开始,重点考察均摊分析思维。

延伸思考

Q:每个操作的最坏时间复杂度是多少?

A:pop 最坏 O(n)(需要转移时)。但均摊下来是 O(1)——因为一个元素被转移后不会再次转移。

Q:用一个栈能实现队列吗?

A:不能。一个栈无法反转元素顺序。两个栈是最少需要。

Q:三个栈能做得更好吗?

A:不能提升均摊复杂度。两个栈已经是最优。

Q:_transfer 为什么要 while self.in_stack 而不是 if

A:输入栈可能有多个元素需要转移。一次转移必须倒空整个输入栈,否则弹出顺序不对。

Q:用 list 模拟栈的 pop 性能如何?

A:Python list 的 pop 是 O(1),尾部操作。这里完美适配。

易错点

  • _transfer 只在 out_stack 为空时调用,不能每次都转移

  • 转移必须要倒空 in_stack 的全部元素

  • peek 不弹出元素,但也要调用 _transfer

  • empty 需要检查两个栈都为空


生活类比

用栈实现队列 → 两摞盘子

脏盘子一股脑摞上去(输入栈),要洗的时候整摞倒到干净水池那边(输出栈)。

倒一次,顺序就反了两次 → 最早进来的最早出去。

洗碗阿姨很聪明:干净水池里还有盘子就不倒——不够了再倒新的。

这就是”输出栈非空不移转”的精髓。

数据结构的本质就是管理顺序——LIFO 转 FIFO,就差一次反转。


相关题目

题目关系
225用队列实现栈对偶题目
155最小栈同为设计题

→ 返回题单:LeetCode学习路线图 > 三、栈与队列

速记卡(面试闪卡)

Q1:一句话讲清「232. 用栈实现队列(Implement Queue using Stacks)」到底是什么?

A:两个栈实现 FIFO 队列:一个输入栈收 push,弹出时整摞倒进输出栈顺序即反转,输出栈空才转移,均摊 O(1)。

Q2:为什么两个栈能变队列? —— 怎么理解?

A:像食堂两摞盘子:脏盘子往上叠(输入栈),整摞倒到干净那边(输出栈),顺序反了两次=原顺序,最早来的最先被拿走。核心洞察:两次反转=原顺序。

Q3:均摊 O(1) 怎么来的? —— 怎么理解?

A:不是每次 pop 都倒,而是输出栈空了才倒一整摞。每个盘子最多经历入栈→出栈→入另一栈→出另一栈四次操作,n 次总 O(n),平均每次 O(1)。像洗碗阿姨”不够了再倒新的”。

Q4:易错点有哪些? —— 怎么理解?

A:_transfer 只在 out_stack 空时调用且必须用 while 倒空整个 in_stack;peek 也要调 _transfer 但不弹出;empty 要看两个栈都空。一个栈无法反转顺序,两个是最少。

Q5:相关扩展? —— 怎么理解?

A:对偶题 225 用队列实现栈;155 最小栈同为设计题。重点考均摊分析思维:pop 最坏 O(n) 但均摊 O(1),因为一个元素转移后不会再转移。

Q6:核心速记主线有哪些?

  • 思路:输入栈收、输出栈取,倒一次反转顺序

  • 关键:输出栈空才转移,均摊 O(1)

  • 操作:push 入 in、pop/peek 触发转移取 out 顶、empty 查双栈

  • 易错:while 倒空 in_stack、peek 也要转移

口诀

A:两摞盘子倒一遭,

反转两次序就好;

干净那边有就取,

没了再倒不瞎跑。

相关链接