力扣刷题#23-0232-用栈实现队列

力扣刷题#23-0232-用栈实现队列

题目

仅使用两个栈实现先入先出队列。应支持 pushpoppeekempty 操作。

输入:
["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仅负责文本修饰,综合获得以上内容。

posted @ 2026-06-29 16:52  STA_running  阅读(4)  评论(0)    收藏  举报