SWE · Phone Screen Coding

Microsoft 两个栈实现队列:延迟倒栈与均摊 O(1)

用入栈与出栈两层维护 FIFO;证明每个元素最多倒栈一次,覆盖交错入队出队和空队列操作。

题目重点:用入栈与出栈两层维护 FIFO;证明每个元素最多倒栈一次,覆盖交错入队出队和空队列操作。

题意与假设

这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。

  • 训练 API 为 push、pop、peek、empty;元素可为任意 Python 对象。
  • 空队列 pop/peek 抛 IndexError;题目只给题名,具体语言和错误契约须现场确认。

解题路线

  1. 新元素只压入 inbox。要读队首时,如果 outbox 非空,栈顶就是最早尚未出队元素。
  2. 若 outbox 为空,将 inbox 元素全部逐个弹出压到 outbox;顺序反转一次,最早入队元素来到 outbox 栈顶。
  3. 只有 outbox 清空时才倒栈;在它尚有旧元素时把新元素倒进来会破坏 FIFO。
  4. 每元素 push 入 inbox 一次、倒到 outbox 一次、最终弹出一次,所以虽然单次 pop 可能 O(n),长期均摊每次 O(1)。

手动走一遍

依次 push 1、2,首次 pop 触发倒栈,outbox 从底到顶是 2、1,弹得 1。此时再 push 3,peek 仍应看到 outbox 的 2;待 2 出队后才倒入 3。

Python 参考实现

下面的代码按上文假设实现,适合本地运行并追加自己的测试用例。

代码区可横向滚动;使用键盘时,先聚焦代码区再按方向键查看。

class TwoStackQueue:
    def __init__(self) -> None:
        self.inbox: list[object] = []
        self.outbox: list[object] = []

    def push(self, value: object) -> None:
        self.inbox.append(value)

    def _refill(self) -> None:
        if not self.outbox:
            while self.inbox:
                self.outbox.append(self.inbox.pop())

    def peek(self) -> object:
        self._refill()
        if not self.outbox:
            raise IndexError("peek from empty queue")
        return self.outbox[-1]

    def pop(self) -> object:
        self._refill()
        if not self.outbox:
            raise IndexError("pop from empty queue")
        return self.outbox.pop()

    def empty(self) -> bool:
        return not self.inbox and not self.outbox


if __name__ == "__main__":
    queue = TwoStackQueue()
    queue.push(1)
    queue.push(2)
    assert queue.pop() == 1 and queue.peek() == 2

复杂度

push 最坏 O(1),pop/peek 最坏 O(n)、均摊 O(1);empty O(1)。两栈总共保存每个未出队元素一次,空间 O(n)。均摊可用势函数 2×|inbox| 形式化说明。

易错点与追问

容易失分的边界

  • 每次 pop 都把两个栈来回倒,虽正确但可能 O(n) 每次。
  • outbox 未空就把 inbox 新元素转入,队序错乱。
  • 把 empty 只写成 inbox 为空,漏看 outbox。
  • 声称所有单次 pop 均 O(1),忽略触发转移的那次。

继续深挖

  • 如果多线程并发 push/pop,哪些状态转移要加锁?
  • 如何用一个队列实现栈?
  • 若要求 pop 最坏 O(1),两栈懒倒方案能否满足?
✓已复制微信号!