(huawei)最小栈

最小栈:O(1)时间获取栈最小值的双栈解法详解

引言:为什么需要"最小栈"?

在常规栈(Stack)数据结构中,我们可以轻松实现push(入栈)、pop(出栈)、top(获取栈顶元素)三个核心操作,且时间复杂度均为O(1) 。但在实际开发中,我们经常会遇到一个需求------快速获取当前栈中的最小值,比如在表达式计算、单调栈相关算法中。

如果直接使用常规栈实现"获取最小值"功能,最直接的思路是遍历整个栈查找最小值,此时时间复杂度会退化为O(n) (n为栈中元素个数),在数据量较大时效率极低。因此,我们需要设计一种特殊的栈结构(即"最小栈"),让getMin(获取最小值)操作也能达到O(1) 时间复杂度。

核心思路:双栈协同实现最小栈

要让getMin操作达到O(1),关键在于"提前记录最小值 "------用一个辅助栈同步存储主栈中的"当前最小值",通过双栈协同确保辅助栈的栈顶始终是主栈的最小值。

双栈分工

  • 主栈(min_stack1) :存储所有入栈元素,承担常规栈的pushpoptop功能。
  • 辅助栈(min_stack2):仅存储主栈中的"阶段性最小值",栈顶元素始终是当前主栈的最小值。

核心规则(重点!)

辅助栈的操作需要严格遵循以下规则,才能保证栈顶始终是最小值:

  1. 入栈(push)

    • 先将元素压入主栈;
    • 若辅助栈为空,或当前元素小于等于辅助栈顶元素,则将该元素也压入辅助栈("小于等于"是为了处理重复最小值的情况,避免漏记)。
  2. 出栈(pop)

    • 先判断主栈顶元素是否与辅助栈顶元素相等(若相等,说明当前最小值即将被弹出,辅助栈需同步弹出);
    • 再将主栈顶元素弹出。
  3. 获取最小值(getMin):直接返回辅助栈顶元素(此时栈顶已保证是主栈当前最小值)。

  4. 获取栈顶(top):直接返回主栈顶元素(与常规栈一致)。

代码实现与逐行解析(C++版)

下面基于题目中的代码,逐函数拆解实现逻辑,并补充关键注释。

完整代码

cpp 复制代码
#include <stack>
using namespace std;

class MinStack {
    // 主栈:存储所有元素
    stack<int> min_stack1;
    // 辅助栈:存储当前最小值,栈顶始终是主栈的最小值
    stack<int> min_stack2;
public:
    /** 构造函数:初始化栈(默认构造即可,无需额外操作) */
    MinStack() {
        // stack容器的默认构造函数已完成初始化,无需手动清空
    }
    
    /** 入栈操作:主栈必压,辅助栈按需压 */
    void push(int x) {
        // 1. 元素先压入主栈
        min_stack1.push(x);
        
        // 2. 辅助栈为空 或 当前元素<=辅助栈顶(保证最小值不丢失),则压入辅助栈
        if (min_stack2.empty() || min_stack2.top() >= x) {
            min_stack2.push(x);
        }
    }
    
    /** 出栈操作:主栈必弹,辅助栈按需弹 */
    void pop() {
        // 1. 若主栈顶等于辅助栈顶(当前最小值要被弹出),辅助栈同步弹出
        if (min_stack1.top() == min_stack2.top()) {
            min_stack2.pop();
        }
        
        // 2. 主栈弹出顶部元素
        min_stack1.pop();
    }
    
    /** 获取主栈顶元素 */
    int top() {
        return min_stack1.top();
    }
    
    /** 获取当前栈的最小值(直接返回辅助栈顶) */
    int getMin() {
        return min_stack2.top();
    }
};

/** 实例化与调用示例 */
// int main() {
//     MinStack* obj = new MinStack();
//     obj->push(-2);  // 主栈:[-2],辅助栈:[-2]
//     obj->push(0);   // 主栈:[-2,0],辅助栈:[-2](0 > -2,不压辅助栈)
//     obj->push(-3);  // 主栈:[-2,0,-3],辅助栈:[-2,-3](-3 <= -2,压辅助栈)
//     cout << "当前最小值:" << obj->getMin() << endl;  // 输出 -3
//     obj->pop();     // 主栈弹出-3,辅助栈同步弹出-3 → 主栈:[-2,0],辅助栈:[-2]
//     cout << "栈顶元素:" << obj->top() << endl;       // 输出 0
//     cout << "当前最小值:" << obj->getMin() << endl;  // 输出 -2
//     delete obj;     // 释放内存
//     return 0;
// }

关键细节答疑

  1. 为什么辅助栈要存"小于等于"的元素,而不是"小于"?

    假设主栈依次入栈 2 → 2

    • 若辅助栈只存"小于"的元素:第一次入栈2(辅助栈[2]),第二次入栈2(2不小于辅助栈顶,不存);
    • 当主栈弹出第一个2时,主栈剩余[2],但辅助栈已空,此时getMin会报错。
      因此,"小于等于"能确保重复最小值被同步记录,避免最小值丢失。
  2. 出栈时为什么要先判断辅助栈,再弹主栈?

    若先弹主栈,主栈顶元素已被删除,无法再与辅助栈顶对比,会导致辅助栈的最小值无法同步更新。

复杂度分析

操作 时间复杂度 空间复杂度
push(x) O(1) O(n)
pop() O(1) O(n)
top() O(1) O(n)
getMin() O(1) O(n)
  • 时间复杂度 :所有操作均为栈的基础操作(push/pop/top),均为常数时间O(1)。
  • 空间复杂度 :最坏情况下(主栈元素严格递减,如5→4→3→2→1),辅助栈需存储所有元素,空间复杂度为O(n)(n为主栈元素个数)。

拓展思考:单栈解法 vs 双栈解法

除了双栈法,还有一种常见的"单栈解法"------用栈存储"元素值+当前最小值"的键值对(pair<int, int>),每次入栈时计算新的最小值(当前元素与栈顶最小值的较小者)。

单栈解法示例代码

cpp 复制代码
class MinStack {
    stack<pair<int, int>> st;  // pair<当前元素, 当前栈最小值>
public:
    MinStack() {}
    
    void push(int x) {
        if (st.empty()) {
            st.push({x, x});  // 空栈时,元素本身就是最小值
        } else {
            // 新最小值 = min(当前元素, 栈顶最小值)
            int new_min = min(x, st.top().second);
            st.push({x, new_min});
        }
    }
    
    void pop() {
        st.pop();  // 弹出键值对,最小值同步更新
    }
    
    int top() {
        return st.top().first;  // 返回当前元素
    }
    
    int getMin() {
        return st.top().second;  // 返回当前最小值
    }
};

两种解法对比

维度 双栈解法 单栈解法
逻辑直观性 稍复杂(需理解双栈协同) 更直观(键值对绑定关系)
空间开销 最坏O(n),最好O(1)(主栈递增时) 固定O(n)(每个元素都带最小值)
代码简洁度 略繁琐(两个栈操作) 更简洁(单个栈操作)

实际开发中可根据需求选择:若追求空间最优(主栈递增场景多),选双栈法;若追求代码简洁和直观,选单栈法。

总结

最小栈的核心是"用辅助空间换时间 "------通过额外的辅助栈(或键值对)提前记录最小值,将getMin操作从O(n)优化到O(1),同时保证pushpop操作仍为O(1)。

双栈解法的关键在于辅助栈的"按需入栈/出栈"规则(小于等于入栈、相等出栈),只要掌握这一规则,就能轻松实现高效的最小栈。建议结合文中的main函数测试用例,手动模拟栈的变化过程,加深对逻辑的理解!

相关推荐
m0_736927047 小时前
Spring Boot项目中如何实现接口幂等
java·开发语言·spring boot·后端·spring·面试·职场和发展
渡我白衣7 小时前
C++:链接的两难 —— ODR中的强与弱符号机制
开发语言·c++·人工智能·深度学习·网络协议·算法·机器学习
小龙报7 小时前
《算法通关指南:数据结构和算法篇 --- 顺序表相关算法题》--- 1.移动零,2.颜色分类
c语言·开发语言·数据结构·c++·算法·学习方法·visual studio
再睡一夏就好7 小时前
【C++闯关笔记】使用红黑树简单模拟实现map与set
java·c语言·数据结构·c++·笔记·语法·1024程序员节
ceffans7 小时前
PDF文档中表格以及形状解析-后续处理(线段生成最小多边形)
c++·windows·算法·pdf
前端老宋Running8 小时前
前端防抖与节流一篇讲清楚
前端·面试
小龙报9 小时前
《算法通关指南:数据结构和算法篇 --- 顺序表相关算法题》--- 询问学号,寄包柜,合并两个有序数组
c语言·开发语言·数据结构·c++·算法·学习方法·visual studio
序属秋秋秋9 小时前
《Linux系统编程之开发工具》【编译器 + 自动化构建器】
linux·运维·服务器·c语言·c++·自动化·编译器
●VON9 小时前
双非大学生自学鸿蒙5.0零基础入门到项目实战 -《基础篇》
android·华为·harmonyos·鸿蒙