
示例工程教程【免费下载链接】leetcodeLeetCode solutions in any programming language | 多种编程语言实现 LeetCode、《剑指 Offer第 2 版》、《程序员面试金典第 6 版》题解项目地址https://gitcode.com/doocs/leetcode点击查看免费下载本文以 doocs/leetcode 仓库中《程序员面试金典第 6 版》题解目录下的 面试题 03.02. 栈的最小值 为核心系统讲解如何设计一个支持push、pop、top、getMin四种操作且全部达到 O(1) 时间复杂度的最小值栈并逐语言剖析仓库中 Python3、Java、C、Go、TypeScript、Rust、C#、Swift 八种官方题解的实现细节与变体思路。读完本文你将掌握双栈同步维护前缀最小值这一经典设计模式能够独立复现并应对同类辅助栈面试题。题目背景在 O(1) 时间内回答栈里最小是谁本题出自 LeetCode《程序员面试金典》面试题 03.02题目编号 Min Stack仓库路径 lcci/03.02.Min Stack/README.md英文版见 README_EN.md。题目要求设计一个栈在常规栈支持的push与pop之外额外提供min即getMin函数返回栈元素中的最小值并且push、pop和min操作的时间复杂度都必须为 O(1)。题目给出的操作示例MinStack minStack new MinStack(); minStack.push(-2); minStack.push(0); minStack.push(-3); minStack.getMin(); -- 返回 -3. minStack.pop(); minStack.top(); -- 返回 0. minStack.getMin(); -- 返回 -2.核心难点在于如果getMin每次都扫描全部元素求最小值该操作就是线性的O(n)无法与 O(1) 的push/pop匹配。必须在入栈/出栈的过程中把最小值信息存起来。解法一双栈——用辅助栈同步记录每个前缀的最小值核心思想仓库题解采用**双栈Double Stack**方案思路可以归纳为三点前缀最小值在压入时即可确定每当一个新元素入栈栈内当前最小值要么是上一个最小值要么是新元素本身二者取小即可无需扫描全栈弹出时自动回退元素弹出后栈回到了上一个前缀状态最小值也随之回退到上一个前缀的最小值天然满足栈的 LIFO 语义辅助栈与数据栈严格同步额外维护一条与数据栈同步弹出的当前最小栈getMin只需读取辅助栈栈顶即为 O(1)。具体地用stk1存储数据用stk2存储当前栈中的最小值初始时stk2中放入一个极大值各语言的∞表示见下文。四种操作规则为操作stk1数据栈stk2最小值栈时间复杂度push(x)压入x压入min(x, stk2[-1])即上一个最小值与新元素取小O(1)pop()弹出栈顶同步弹出栈顶随前缀回退O(1)top()直接返回stk1栈顶——O(1)getMin()——直接返回stk2栈顶O(1)该方案的时间复杂度每个操作均为 O(1)空间复杂度 O(n)stk2与数据栈同规模。Python3仓库 Solution.py 中利用 Python 列表天然支持append/pop/下标访问的特性实现inf表示正无穷作为初始极大值class MinStack: def __init__(self): initialize your data structure here. self.s [] self.mins [inf] def push(self, val: int) - None: self.s.append(val) self.mins.append(min(self.mins[-1], val)) def pop(self) - None: self.s.pop() self.mins.pop() def top(self) - int: return self.s[-1] def getMin(self) - int: return self.mins[-1] # Your MinStack object will be instantiated and called as such: # obj MinStack() # obj.push(val) # obj.pop() # param_3 obj.top() # param_4 obj.getMin()要点self.mins [inf]保证首次push时min(inf, val)恒等于val避免了空栈取最小值的特判。Java仓库 Solution.java 使用ArrayDeque作为栈实现构造时向stk2压入Integer.MAX_VALUEclass MinStack { private DequeInteger stk1 new ArrayDeque(); private DequeInteger stk2 new ArrayDeque(); /** initialize your data structure here. */ public MinStack() { stk2.push(Integer.MAX_VALUE); } public void push(int x) { stk1.push(x); stk2.push(Math.min(x, stk2.peek())); } public void pop() { stk1.pop(); stk2.pop(); } public int top() { return stk1.peek(); } public int getMin() { return stk2.peek(); } }注意stk2的栈顶在push时是尚未压入新值前的上一个最小值因此Math.min(x, stk2.peek())与上文min(x, stk2[-1])完全等价。C仓库 Solution.cpp 使用标准库std::stack用INT_MAX作为初始极大值class MinStack { public: /** initialize your data structure here. */ MinStack() { stk2.push(INT_MAX); } void push(int x) { stk1.push(x); stk2.push(min(x, stk2.top())); } void pop() { stk1.pop(); stk2.pop(); } int top() { return stk1.top(); } int getMin() { return stk2.top(); } private: stackint stk1; stackint stk2; };Go仓库 Solution.go 用切片模拟栈Constructor返回结构体时把stk2初始化为[]int{math.MaxInt32}出栈通过切片截断stk[:len(stk)-1]完成type MinStack struct { stk1 []int stk2 []int } /** initialize your data structure here. */ func Constructor() MinStack { return MinStack{[]int{}, []int{math.MaxInt32}} } func (this *MinStack) Push(x int) { this.stk1 append(this.stk1, x) this.stk2 append(this.stk2, min(x, this.stk2[len(this.stk2)-1])) } func (this *MinStack) Pop() { this.stk1 this.stk1[:len(this.stk1)-1] this.stk2 this.stk2[:len(this.stk2)-1] } func (this *MinStack) Top() int { return this.stk1[len(this.stk1)-1] } func (this *MinStack) GetMin() int { return this.stk2[len(this.stk2)-1] }Go 代码中的min为 Go 1.21 内置函数math.MaxInt32需要import math。TypeScript仓库 Solution.ts 直接使用数组getMin在mins为空时返回Infinity因此在push中通过Math.min(this.getMin(), x)亦可正确写入首个最小值class MinStack { stack: number[]; mins: number[]; constructor() { this.stack []; this.mins []; } push(x: number): void { this.stack.push(x); this.mins.push(Math.min(this.getMin(), x)); } pop(): void { this.stack.pop(); this.mins.pop(); } top(): number { return this.stack[this.stack.length - 1]; } getMin(): number { return this.mins.length 0 ? Infinity : this.mins[this.mins.length - 1]; } }C#仓库 Solution.cs 使用System.Collections.Generic.Stackint构造时将int.MaxValue压入stk2public class MinStack { private Stackint stk1 new Stackint(); private Stackint stk2 new Stackint(); /** initialize your data structure here. */ public MinStack() { stk2.Push(int.MaxValue); } public void Push(int x) { stk1.Push(x); stk2.Push(Math.Min(x, GetMin())); } public void Pop() { stk1.Pop(); stk2.Pop(); } public int Top() { return stk1.Peek(); } public int GetMin() { return stk2.Peek(); } }C# 与 Java 同属栈顶即上一个最小值的写法stk2.Push(Math.Min(x, GetMin()))中GetMin()取到的是压入前的旧栈顶。Swift仓库 Solution.swift 用数组实现init中将stk2初始化为[Int.max]class MinStack { private var stk1: [Int] private var stk2: [Int] init() { stk1 [] stk2 [Int.max] } func push(_ x: Int) { stk1.append(x) stk2.append(min(x, stk2.last!)) } func pop() { stk1.removeLast() stk2.removeLast() } func top() - Int { return stk1.last! } func getMin() - Int { return stk2.last! } }解法二变体Rust 的最小值栈按需入栈优化仓库 Solution.rs 没有照搬每压必入的双栈而是采用仅在出现新的或相等的最小值时才入辅助栈的变体从而在数据单调递增时显著节省辅助栈空间use std::collections::VecDeque; struct MinStack { stack: VecDequei32, min_stack: VecDequei32, } impl MinStack { /** initialize your data structure here. */ fn new() - Self { Self { stack: VecDeque::new(), min_stack: VecDeque::new(), } } fn push(mut self, x: i32) { self.stack.push_back(x); if self.min_stack.is_empty() || *self.min_stack.back().unwrap() x { self.min_stack.push_back(x); } } fn pop(mut self) { let val self.stack.pop_back().unwrap(); if *self.min_stack.back().unwrap() val { self.min_stack.pop_back(); } } fn top(self) - i32 { *self.stack.back().unwrap() } fn get_min(self) - i32 { *self.min_stack.back().unwrap() } }这段代码与双栈同步方案在push/pop上的差异值得注意push只有min_stack为空或新元素x不大于当前辅助栈栈顶即判断通过时才把x写入min_stack否则说明x不是新最小值无需记录pop先从stack弹出val仅当val恰好等于min_stack栈顶时才同步弹出辅助栈——因为只有最小值被弹走当前最小值才会回退条件中的保留了相等元素的重复记录例如连续压入两个-3辅助栈会记录两次避免弹出其中一个-3后最小值栈被错误清空。这是该变体正确性的关键细节。两种方案各操作的时间复杂度均为 O(1)空间复杂度上双栈同步版固定为 O(n)Rust 变体在最坏情况严格递减序列下同样为 O(n)但在单调不减序列下辅助栈可退化为常数规模属于最坏相同、平均更省的优化写法。复杂度与正确性小结时间复杂度push、pop、top、getMin全部为 O(1)。top读数据栈栈顶、getMin读辅助栈栈顶都是常数时间push/pop只做一次压栈/弹栈与一次比较。空间复杂度双栈同步方案为 O(n)其中 n 为栈中元素个数。正确性依据最小值具有前缀单调性——栈从底部到栈顶的任意前缀其最小值在元素压入时即被固化在辅助栈的对应位置上弹出元素等价于回退到上一前缀辅助栈同步弹出后栈顶依然对应当前前缀的最小值。因此无论执行序列如何交织getMin返回值始终与栈内实际最小值一致。仓库中该题的全部实现均可对照查看Python3Solution.py、JavaSolution.java、CSolution.cpp、GoSolution.go、TypeScriptSolution.ts、RustSolution.rs、C#Solution.cs、SwiftSolution.swift多语言代码与本文完全一致。延伸同目录下的相关栈题目本类辅助栈设计在《程序员面试金典》栈与队列章节中反复出现可在仓库 lcci 目录下继续练习03.01. 三合一Three in One单个数组实现三个栈03.03. 堆盘子Stack of Plates多栈分层管理03.04. 化栈为队Implement Queue using Stacks双栈模拟队列与本题双栈各司其职的思维同源03.05. 栈排序Sort of Stacks借助辅助栈完成排序是辅助栈思路的又一典型应用。掌握了用辅助数据结构换取 O(1) 查询、与主结构同步维护这一模式应对上述题目时便能举一反三。赞分享示例工程教程【免费下载链接】leetcodeLeetCode solutions in any programming language | 多种编程语言实现 LeetCode、《剑指 Offer第 2 版》、《程序员面试金典第 6 版》题解项目地址https://gitcode.com/doocs/leetcode点击查看免费下载相关推荐doocs/leetcode 题解深度解析面试题 03.02 栈的最小值Min Stack双栈 O(1) 实现doocs/leetcode 题解深度解析面试题 03.02 栈的最小值Min Stack双栈 O 1 实现 本文基于 doocs/leetcode 开源示例工程教程doocs/leetcode 题解精讲面试题 03.04 化栈为队双栈实现队列七种语言doocs/leetcode 题解精讲面试题 03.04 化栈为队双栈实现队列七种语言 本文以 doocs/leetcode 开源题解仓库中 《程序员面示例工程教程doocs/leetcode 题解深剖面试题 03.05 栈排序Sort of Stacks——辅助栈实现最小元素永远在栈顶doocs/leetcode 题解深剖面试题 03.05 栈排序Sort of Stacks——辅助栈实现最小元素永远在栈顶 本篇技术指南以 dooc示例工程教程上一篇终极指南如何用eqMac实现macOS系统级音频调校与专业级音质优化 下一篇My Minimum Viable Day — YYYY-MM-DD创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考