力扣刷题#22-0155-最小栈

力扣刷题#22-0155-最小栈

题目

设计一个支持 pushpoptop 操作,并能在常数时间内检索到最小元素的栈。

实现 MinStack 类:

方法 说明
MinStack() 初始化栈对象
void push(int value) 将元素 value 推入栈
void pop() 删除栈顶元素
int top() 获取栈顶元素
int getMin() 获取栈中最小元素
输入:
["MinStack","push","push","push","getMin","pop","top","getMin"]
[[],[-2],[0],[-3],[],[],[],[]]

输出:
[null,null,null,null,-3,null,0,-2]

我的思路之旅

第一反应:排序数组?太蠢了

看到 getMin 要 O(1),我第一反应是——在 push 里塞个数组,每次 push 就排序,getMin 直接返回第一个元素。

但马上意识到这太慢了:每 push 一次就是 O(n log n)。栈的优势全被浪费了。


关键洞察:辅助栈存"历史最小值"

既然 getMin 要在最值变化时立即感知,那最简单的方式就是——用一个独立的栈,专门记录"到当前层为止出现过的最小值"

push(x):
  minStack.push( min(x, minStack.top() 或 x) )   ← 同步记录
  mainStack.push(x)

这样 getMin() 直接看 minStack.top(),O(1)。


跑一遍例子

操作序列: push 5, push 3, push 7, push 2, getMin, pop, getMin
操作 mainStack minStack minStack.top() 逻辑
push 5 [5] [5] 栈空,5 就是最小值
push 3 [5,3] [5,3] min(3, 5)=3,压入 3
push 7 [5,3,7] [5,3,3] min(7, 3)=3,压入 3
push 2 [5,3,7,2] [5,3,3,2] min(2, 3)=2,压入 2
getMin 返回 2
pop [5,3,7] [5,3,3] 两栈同步弹出
getMin 返回 3

pop 之后最小值自动回退到 3——不需要扫描全栈重建最值。


我的 AC 代码

class MinStack {
public:
    stack<int> minstack, mainstack;
    MinStack() { }
    
    void push(int value) {
        if (minstack.empty()) {
            minstack.push(value);
        } else {
            minstack.push(min(minstack.top(), value));
        }
        mainstack.push(value);
    }
    
    void pop() {
        mainstack.pop();
        minstack.pop();
    }
    
    int top() {
        return mainstack.top();
    }
    
    int getMin() {
        return minstack.top();
    }
};

代码逐段解析

第 3 行:成员变量

stack<int> minstack, mainstack;

两个栈都是类的成员变量,不是局部变量。所有方法共享它们。调用 push/pop 都是操作同一个 minstackmainstack

第 4 行:构造函数

MinStack() { }

两个 stack<int> 默认构造就是空栈,构造函数不需要做任何事。

第 6-12 行:push

void push(int value) {
    if (minstack.empty()) {
        minstack.push(value);          // 栈空 → 当前值就是最小值
    } else {
        minstack.push(min(minstack.top(), value));  // 和前一个最小值比
    }
    mainstack.push(value);
}

minstack 的层数和 mainstack 始终一样。每一层存的都是"从栈底到当前层的最小值"。

第 14-17 行:pop

void pop() {
    mainstack.pop();
    minstack.pop();
}

两栈同步弹出。pop 后最小值自动回退到上一层的记录。

第 19-25 行:top 和 getMin

int top() {
    return mainstack.top();
}

int getMin() {
    return minstack.top();
}

各看各的栈顶,都是 O(1)。


踩坑记录

坑 1:栈写成了局部变量

// ❌ 第一版
MinStack() {
    stack<int> minstack, mainstack;   // 局部变量,构造函数结束就销毁
}

构造函数退出后,push/pop/top/getMin 里用的 minstackmainstack 压根不存在,编译报错。

修复:移到 public:(或 private:)作为类成员变量。

坑 2:std::min 需要 <algorithm>

minstack.push(min(minstack.top(), value));

力扣环境默认包含了,但本地编译须加 #include <algorithm>


优化分析

为什么跑了 168ms / 5%?

首次提交跑出 168ms 不代表代码慢——力扣服务器负载和测试用例分配有波动。同一份代码重新提交常常跳到 10ms 以内。

但确实存在一个可优化的点:minStack 里存了大量重复值

push 5 → minStack: [5]
push 3 → minStack: [5, 3]
push 7 → minStack: [5, 3, 3]    ← 7 大于最小值 3,没必要多存一个 3
push 2 → minStack: [5, 3, 3, 2]

优化:只在最小值被打破时才压入 minStack(惰性 push):

void push(int value) {
    mainstack.push(value);
    if (minstack.empty() || value <= minstack.top()) {
        minstack.push(value);     // 只有 ≤ 当前最小值才压
    }
}

void pop() {
    if (mainstack.top() == minstack.top()) {
        minstack.pop();            // 弹出的恰是最小值,同步弹
    }
    mainstack.pop();
}
对比 原版 惰性版
push 5→3→7→2 minStack=[5,3,3,2] minStack=[5,3,2]
getMin O(1) O(1)
内存 每次 push 都分配 仅最值打破时分配

注意 <= 不能写成 <——用 < 的话连续 push 两个相同最小值时,pop 一个就会把唯一记录弹出,getMin 拿不到正确值。

极速方案(1ms 提交的秘诀)

力扣上 1ms 的提交通常用差值编码法——只用一个栈,不存值本身,存"值与当前最小值的差值",通过数学编码还原:

push(x): 如果 x < min,推入 2x - min 并更新 min;否则推入 x - min
pop:    如果栈顶 < 0,恢复上一个 min = min - 栈顶
top:    如果栈顶 < 0,返回 min;否则返回 min + 栈顶

为什么快?因为完全避开了 std::min 函数调用和双栈同步开销,纯加减法。但这属于"卷到底"的极限优化,面试中标准双栈版已足够。


关键点总结

关键点 说明
核心结构 双栈——主栈存数据,辅助栈存"历史最小值"
push 两栈同步压入,minStack 存 min(top, value)
pop 两栈同步弹出,最小值自动回退
getMin 看 minStack 栈顶,O(1)
成员 vs 局部 栈必须是成员变量,不能写在构造函数里

复杂度分析

维度 说明
时间复杂度 全部 O(1) push/pop/top/getMin 均为常数
空间复杂度 O(n) 两栈各存 n 个元素

本文档由 AI 辅助生成,作者提供问题,思路和代码,AI仅负责文本修饰,综合获得以上内容。

posted @ 2026-06-28 18:36  STA_running  阅读(3)  评论(0)    收藏  举报