155. 最小栈

题目

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

实现 MinStack 类:

MinStack() 初始化堆栈对象。
void push(int val) 将元素val推入堆栈。
void pop() 删除堆栈顶部的元素。
int top() 获取堆栈顶部的元素。
int getMin() 获取堆栈中的最小元素。

示例 1:

输入:
[“MinStack”,“push”,“push”,“push”,“getMin”,“pop”,“top”,“getMin”]
[[],[-2],[0],[-3],[],[],[],[]]

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

解释:
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.getMin(); --> 返回 -3.
minStack.pop();
minStack.top(); --> 返回 0.
minStack.getMin(); --> 返回 -2.

提示:

-231 <= val <= 231 - 1
pop、top 和 getMin 操作总是在 非空栈 上调用
push, pop, top, and getMin最多被调用 3 * 104 次

代码

class MinStack {
   
   
public:
    MinStack() {
   
   
        
    }
    
    void push
<think>我们讨论的是LeetCode 155题“最小”。题目要求设计一个,除了常规的push、pop、top操作外,还能在常数时间内检索到中的最小元素。根据引用[1][2][3][4][5],我们看到了几种不同的实现方法,现在需要分析并给出最优解法。 ### 方法对比 1. **双法(两个)**:引用[1][3]使用两个,一个存储数据,另一个存储当前最小值。每次push时,如果新元素小于等于最小顶,则压入最小;pop时,如果弹出的元素等于最小顶,则最小也弹出。但注意,引用[1]和[3]的实现中,最小每次push都会压入当前最小值(即使新元素比当前最小值大,也重复压入当前最小值)。这样,两个的大小始终保持一致,pop时同时弹出即可。 2. **单法(使用数组/元组)**:引用[2]使用一个,但中每个元素是一个数组(或元组),其中第一个元素是当前值,第二个元素是从底到该元素时的最小值。这样,每次push时,当前元素的最小值可以通过比较新元素和顶元素的最小值得到。这种方法只需要一个,但每个元素存储的信息更多。 3. **优化双法(最小非同步压入)**:引用[4]提到,最小只有在遇到小于等于当前最小值的元素时才压入。这样,最小中的元素不会重复,且数量会少于主。pop时,只有弹出的元素等于最小顶时,最小才弹出。 ### 最优解法分析 - **时间效率**:三种方法在push、pop、top、getMin操作的时间复杂度都是$O(1)$,满足题目要求。 - **空间效率**:方法1(双同步)在最坏情况下(所有元素递减)空间为$2n$。方法2(单)每个元素存储两个值,空间也是$2n$。方法3(最小非同步)在最好情况下(元素递增)最小只存储一个元素,空间为$n+1$;最坏情况(递减)空间为$2n$。因此,方法3在空间上更优。 - **实现复杂度**:方法1和方法2实现简单,且pop操作不需要判断,直接弹出。方法3在pop时需要判断主弹出的元素是否等于最小顶,如果相等则弹出最小,实现稍复杂,但节省空间。 ### 最优解法:优化双法(最小非同步压入) 参考引用[4],我们采用以下策略: - 使用两个:主`stack`和最小`min_stack`。 - `push(x)`操作: - 主直接压入`x`。 - 如果最小为空,或者`x`小于等于最小顶元素,则将`x`压入最小。 - `pop()`操作: - 弹出主顶元素,记为`val`。 - 如果`val`等于最小顶元素,则弹出最小顶。 - `top()`:返回主顶。 - `getMin()`:返回最小顶。 **注意**:为什么使用小于等于?考虑连续压入相同最小值的情况,例如:push(0), push(1), push(0)。第一个0压入最小,然后压入1(不压入最小),再压入0,此时0等于最小顶0,所以压入。这样,当pop第一个0时,最小的0被弹出,而第二个0还在,保证了正确性。 ### 代码实现(C++) ```cpp class MinStack { private: stack<int> data_stack; stack<int> min_stack; public: MinStack() {} void push(int val) { data_stack.push(val); // 当最小为空,或者新元素小于等于最小顶时,压入最小 if (min_stack.empty() || val <= min_stack.top()) { min_stack.push(val); } } void pop() { if (data_stack.empty()) return; int val = data_stack.top(); data_stack.pop(); // 如果弹出的元素等于最小顶,则弹出最小 if (val == min_stack.top()) { min_stack.pop(); } } int top() { return data_stack.top(); } int getMin() { return min_stack.top(); } }; ``` ### 示例演算(以引用[5]的操作为例) 操作序列:`push(-2)`, `push(0)`, `push(-3)`, `getMin()`, `pop()`, `top()`, `getMin()` | 操作 | 主底→顶) | 最小底→顶) | 返回值 | 说明 | |----------------|-------------------|---------------------|--------|--------------------------------------| | 初始化 | [] | [] | | | | push(-2) | [-2] | [-2] | | 最小空,压入-2 | | push(0) | [-2,0] | [-2] | | 0>-2,最小不变 | | push(-3) | [-2,0,-3] | [-2,-3] | | -3<-2,压入-3 | | getMin() | | | -3 | 最小顶为-3 | | pop() | [-2,0] | [-2] | | 弹出主-3,等于最小顶-3,弹出 | | top() | | | 0 | 主顶为0 | | getMin() | | | -2 | 最小顶为-2 | ### 空间复杂度分析 - 最坏情况:元素递减,每个新元素都小于前一个,最小和主大小相同,空间复杂度$O(n)$。 - 最好情况:元素递增,最小只存储第一个元素,空间复杂度$O(1)$(最小大小1,主大小n)。 - 平均情况:空间复杂度$O(n)$,但实际使用空间小于等于同步双法。 ### 相关问题 1. 为什么最小的push条件要包含等于的情况? 2. 如果中存储的是对象(而非基本类型),如何修改比较逻辑? 3. 在并发环境下,如何保证最小的线程安全? [^1]: 双同步压入法,空间复杂度为$O(2n)$ [^2]: 单存储元组法,空间复杂度$O(2n)$ [^3]: 双非同步压入法,空间复杂度最坏$O(2n)$,最好$O(1)$ [^4]: 最小只在必要时压入,节省空间 [^5]: 操作序列示例</think>### LeetCode 155 最小最优解法详解 最小要求在常数时间内实现 `push`、`pop`、`top` 和 `getMin` 操作。最优解法采用**双结构 + 非同步压策略**,在保证 $O(1)$ 时间复杂度的同时优化空间效率。 #### 核心思想 1. **双结构**: - `data_stack`:存储常规数据 - `min_stack`:存储当前最小值 2. **非同步压**: - 仅当新元素 $\leq$ `min_stack` 顶时,才压入 `min_stack` - 避免存储冗余的最小值(如引用[4]的优化) #### 时间复杂度分析 | 操作 | 时间复杂度 | 说明 | |----------|------------|--------------------------| | `push()` | $O(1)$ | 操作 + 一次比较 | | `pop()` | $O(1)$ | 操作 + 一次比较 | | `top()` | $O(1)$ | 直接访问顶 | | `getMin()`| $O(1)$ | 直接访问 `min_stack` 顶| #### 空间复杂度分析 - **最坏情况**:$O(2n)$(元素严格递减) - **最佳情况**:$O(n)$(元素递增,`min_stack` 仅存首元素) - **平均情况**:显著优于同步压法(引用[1][3]) --- ### 最优解法代码实现(C++) ```cpp class MinStack { private: stack<int> data_stack; // 主数据 stack<int> min_stack; // 最小值辅助 public: MinStack() {} // 初始化 void push(int val) { data_stack.push(val); // 仅当 min_stack 为空或 val ≤ 当前最小值时压 if (min_stack.empty() || val <= min_stack.top()) { min_stack.push(val); } } void pop() { // 若弹出值等于当前最小值,则同步弹出 min_stack if (data_stack.top() == min_stack.top()) { min_stack.pop(); } data_stack.pop(); } int top() { return data_stack.top(); } int getMin() { return min_stack.top(); // 直接返回最小顶 } }; ``` --- ### 演算示例(引用[5]的操作序列) | 操作 | `data_stack` (底→顶) | `min_stack` (底→顶) | 返回值 | 关键逻辑说明 | |------------------|--------------------------|--------------------------|--------|----------------------------------| | `MinStack()` | `[]` | `[]` | | 初始化空 | | `push(-2)` | `[-2]` | `[-2]` | | `min_stack` 压入首个元素 | | `push(0)` | `[-2, 0]` | `[-2]` | | 0 > -2,不压 `min_stack` | | `push(-3)` | `[-2, 0, -3]` | `[-2, -3]` | | -3 < -2,压入 `min_stack` | | `getMin()` | | | **-3** | 访问 `min_stack.top()` | | `pop()` | `[-2, 0]` | `[-2]` | | 弹出 -3 且等于 `min_stack.top()`| | `top()` | | | **0** | 访问 `data_stack.top()` | | `getMin()` | | | **-2** | `min_stack` 顶回退到 -2 | > **关键点**:`pop()` 时仅当弹出值等于 `min_stack.top()` 才同步弹出,避免最小值丢失(如 `push(0)` 后弹出不影响最小值)[^4]。 --- ### 对比其他解法 1. **同步双法(引用[1][3])**: - `min_stack` 始终与 `data_stack` 同大小 - 空间复杂度恒为 $O(2n)$,存在冗余存储 - 实现简单但空间效率低 2. **单元组法(引用[2])**: - 单存储 `(value, current_min)` - 空间复杂度 $O(2n)$,访问逻辑稍复杂 - 无同步问题,但扩展性受限 > **最优解优势**:空间效率显著提升(尤其对递增序列),代码简洁易维护[^4]。 --- ### 边界处理 1. **空操作**: - `pop()`/`top()`/`getMin()` 需检查空(LeetCode 测试用例保证非空) 2. **重复最小值**: ```cpp push(0); // data_stack=[0], min_stack=[0] push(1); // data_stack=[0,1], min_stack=[0] push(0); // data_stack=[0,1,0], min_stack=[0,0] ``` - 使用 `val <= min_stack.top()` 保证重复最小值被压 - `pop()` 时同步弹出避免过早丢失最小值 --- ### 相关问题 1. 如何处理自定义对象的最小? → 需实现比较运算符(如 `operator<=`)[^4]。 2. 为什么 `min_stack` 压条件是 $\leq$ 而非 $<$? → 防止重复最小值场景下 `getMin()` 错误(见边界处理示例)[^5]。 3. 如何扩展支持多维度最小值(如最大值、中位数)? → 设计多维辅助或使用红黑树结构[^2]。 [^1]: 双同步压入法空间效率低 [^2]: 单元组法扩展性受限 [^3]: 同步双法存在冗余存储 [^4]: 非同步压优化空间效率 [^5]: 重复最小值需特殊处理
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小辉同志

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值