力扣刷题#23-0232-用栈实现队列
力扣刷题#23-0232-用栈实现队列
题目
仅使用两个栈实现先入先出队列。应支持 push、pop、peek、empty 操作。
输入:
["MyQueue", "push", "push", "peek", "pop", "empty"]
[[], [1], [2], [], [], []]
输出:
[null, null, null, 1, 1, false]
我的思路之旅
第一反应:不知道怎么下手
栈是后进先出(LIFO),队列是先进先出(FIFO)。用栈实现队列——这不是用锤子拧螺丝吗?
没思路的时候,从性质出发:栈会把顺序颠倒。那颠倒两次呢?
原始: 1, 2, 3
经过栈1(压入再弹出): 3, 2, 1 ← 反了一次
经过栈2(压入再弹出): 1, 2, 3 ← 又反一次 = 恢复原样!
两次颠倒 = 恢复原始顺序!这就是"两栈模拟队列"的数学原理。
双栈分工
inStack → 只负责收(push 进来就塞这里)
outStack → 只负责出(pop/peek 从这里拿)
要 pop 时,如果 outStack 是空的,把 inStack 全倒过来。
生活化类比:
银行排队。大厅(inStack)里陆续来人。柜员窗口前(outStack)已经排了一队。新来的人不能插到窗口前——他们只能在大厅等。等窗口前的人都办完了,柜员喊一句"下一批",大厅里的人全部移过去排队。
一道完整例子走通
push 1,2,3 → pop → push 4,5 → pop → pop → pop
| 步骤 | inStack | outStack | 说明 |
|---|---|---|---|
| push 1,2,3 | [1,2,3] | [] | |
| pop() | [] | [3,2,1] | 倾倒:全搬,顺序反了→最早进的 1 在顶 |
| 弹出 1 | [] | [3,2] | 返回 1 ✅ |
| push 4,5 | [4,5] | [3,2] | 4,5 进 inStack,outStack 不空→不倒 |
| pop() | [4,5] | [3] | outStack 还有货,直接弹 2 ✅ |
| pop() | [4,5] | [] | 弹 3,outStack 空了 |
| pop() | [] | [5,4] | outStack 空 → 第二次倾倒! |
| 弹出 4 | [] | [5] | 返回 4 ✅ |
出队顺序:1→2→3→4→5。和入队完全一致,FIFO 成立。
关键规则:倾倒只在 outStack 空的时候做,且每次全倒。
我的 AC 代码
class MyQueue {
public:
stack<int> instack;
stack<int> outstack;
MyQueue() { }
void push(int x) {
instack.push(x);
}
void dumpIfNeeded() {
if (outstack.empty()) {
while (!instack.empty()) {
outstack.push(instack.top());
instack.pop();
}
}
}
int pop() {
dumpIfNeeded();
int val = outstack.top();
outstack.pop();
return val;
}
int peek() {
dumpIfNeeded();
return outstack.top();
}
bool empty() {
return instack.empty() && outstack.empty();
}
};
代码逐段解析
第 3 行:两个成员栈
stack<int> instack;
stack<int> outstack;
两个栈都是类的成员变量(放在 public: 区域,力扣可直接访问)。构造函数不需要做任何事——stack<int> 默认构造就是空栈。
第 7-9 行:push — 只管收
void push(int x) {
instack.push(x);
}
一行搞定。push 不涉及任何判断——永远往 instack 塞。队列的"尾部"就是栈的"顶部"。
第 11-18 行:dumpIfNeeded — 核心倾倒逻辑
void dumpIfNeeded() {
if (outstack.empty()) {
while (!instack.empty()) {
outstack.push(instack.top());
instack.pop();
}
}
}
这个辅助方法被 pop() 和 peek() 共用。两步判断:
| 判断 | 动作 |
|---|---|
outstack 空? |
需要倾倒——把 instack 全部搬过来 |
outstack 不空? |
不碰——已经有排好队的旧数据,不能打乱顺序 |
第 20-24 行:pop — 先倾倒再弹出
int pop() {
dumpIfNeeded();
int val = outstack.top();
outstack.pop();
return val;
}
先确保 outstack 有货(倾倒或本来就有),然后取栈顶、弹出、返回。
第 26-28 行:peek — 和 pop 只差一步
int peek() {
dumpIfNeeded();
return outstack.top();
}
和 pop() 几乎一样,只是不执行 outstack.pop()。
第 30-32 行:empty — 两栈都空才是空
bool empty() {
return instack.empty() && outstack.empty();
}
数据可能分散在两个栈里(有的是"新来的"在 instack,有的是"排好队的"在 outstack)。只有两个栈都空了,队列才算空。
我的疑问与澄清
疑问 1:两次倾倒之间 push 新数据,outStack 不会错乱吗?
这是我最大的困惑。操作序列 push 1,2,3 → pop → push 4 → pop——中间 push 进来的 4,会不会被误当成"早就该出的"数据弹出去?
不会。 因为倾倒只在 outStack 空的时候发生。push 4 时 outStack 里还有 [3,2](1 已被弹出),4 进了 instack。下次 pop 时 outStack 不空 → 不触发倾倒 → 4 乖乖在 instack 等着。
outStack 里的数据是"旧数据已排队",instack 里的是"新来的还没排"。pop 优先消费 outStack,保证了先来后到的顺序。
疑问 2:第二次、第三次倾倒时,"倒哪些"呢?不需要记上次倒到哪了吗?
不需要。倾倒的规则是——outStack 空了,就把 inStack 全部倒过去。每次倒完 inStack 都是空的,天然清零,不存在"上次倒到一半"的状态。
第一次倒:[1,2,3] 全部倒过去 → inStack 空
第二次倒:[4,5] 全部倒过去 → inStack 空
不需要标记、不需要计数、不需要边界条件。全倒就行。
疑问 3:判空时会不会因为 outStack 有"残留"而误判?
不会。倾倒 = 搬移,不是复制。outstack.push(instack.top()) 之后紧接 instack.pop()——元素从 instack 移到了 outstack,总数不变。
倾倒前: inStack=[1,2], outStack=[]
倾倒后: inStack=[], outStack=[2,1]
数字 1 和 2 各只有一份,只是换了个位置。
两个栈的数据加起来就是队列的全部内容。两栈都空 → 队列空。
复杂度分析
时间复杂度
| 操作 | 均摊复杂度 | 说明 |
|---|---|---|
| push | O(1) | 直接压栈 |
| pop | 均摊 O(1) | 倾倒看似 O(n),但每个元素最多被倒 1 次 |
| peek | 均摊 O(1) | 同 pop |
| empty | O(1) | 两次判空 |
为什么倾倒不是 O(n)? 每个元素在整个生命周期中只经历一次倾倒:push 进 inStack → 某次倾倒进入 outStack → pop 出去。摊到每次操作上就是 O(1)。
空间复杂度
O(n),两栈合计存 n 个元素。
关键点总结
| 关键点 | 说明 |
|---|---|
| 核心原理 | 两次 LIFO 抵消 = FIFO |
| inStack | 只管 push |
| outStack | 负责 pop/peek |
| 倾倒时机 | outStack 空时才倒,不空就忍着 |
| 倾倒方式 | 全倒,不是选几个倒 |
| 搬移语义 | push-top-pop = 搬移,不是复制 |
本文档由 AI 辅助生成,作者提供问题,思路和代码,AI仅负责文本修饰,综合获得以上内容。

浙公网安备 33010602011771号