最小栈GO实现
[155. 最小栈]https://leetcode.cn/problems/min-stack/
- 分析
- 变形题
- 思路
已解答
中等
设计一个支持 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
, andgetMin
最多被调用3 * 104
次
分析
看的艾府大大的解读,一边搬运一边记录,为了以后自己复习。
引入
给你一个数组 nums,如何计算每个前缀的最小值?
定义 preMin[i] 表示 nums[0] 到 nums[i] 的最小值。
这可以从左到右计算:
preMin[0]=nums[0]。
preMin[1]=min(nums[0],nums[1])。
preMin[2]=min(nums[0],nums[1],nums[2])=min(preMin[1],nums[2])。
preMin[3]=min(nums[0],nums[1],nums[2],nums[3])=min(preMin[2],nums[3])。
……
一般地,我们有
preMin[i]=min(preMin[i−1],nums[i])
作者:灵茶山艾府
链接:https://leetcode.cn/problems/min-stack/solutions/2974438/ben-zhi-shi-wei-hu-qian-zhui-zui-xiao-zh-x0g8/
这道题就是维护前缀和!我看有的直接用了pair这种组合,但是我这个也可以,就懒得改了。
type MinStack struct {stack []intminStack []int
}func Constructor() MinStack {return MinStack{stack: []int{},minStack: []int{math.MaxInt64},}
}func (this *MinStack) Push(val int) {this.stack = append(this.stack, val)tmp := min(this.minStack[len(this.minStack)-1], val)this.minStack = append(this.minStack, tmp)}func (this *MinStack) Pop() {this.stack = this.stack[:len(this.stack)-1]this.minStack = this.minStack[:len(this.minStack)-1]
}func (this *MinStack) Top() int {return this.stack[len(this.stack)-1]
}func (this *MinStack) GetMin() int {return this.minStack[len(this.minStack)-1]
}
变形题
- 改成队列(queue),getMin 返回队列中的最小元素。
- 改成双端队列(deque),getMin 返回双端队列中的最小元素。
答案在:https://codeforces.com/blog/entry/122003
思路
利用一次顺序的颠倒,为反向的出队操作准备好了完全正确的最小值数据。
通过组合两个“最小栈” (MinStack
) 来巧妙地实现一个**“最小队列” (MinQueue
)**。
- 基础组件
MinStack
: 首先实现一个MinStack
。它内部使用一个辅助栈,在每个元素入栈时同步记录当前栈内的最小值。这确保了GetMin
操作的时间复杂度为 O(1)。 MinQueue
的实现:- 使用两个
MinStack
:一个inStack
作为入口(处理所有入队Push
操作),另一个outStack
作为出口(处理所有出队Pop
操作)。 - 核心机制:当需要出队但
outStack
为空时,会触发一次“倾倒”操作,将inStack
的所有元素全部转移到outStack
。这个转移过程自动地将元素顺序颠倒,从而模拟了队列“先进先出”的特性。 - 获取最小值:整个队列的最小值,就是
inStack
的最小值与outStack
的最小值中的较小者,此操作复杂度也为 O(1)。
- 使用两个
通过这种方式,代码将栈的 LIFO (后进先出) 特性转换为队列的 FIFO (先进先出) 特性,并始终保持了高效获取最小元素的能力。
我这里附上一些我这个最小栈版本的最小队列解法
package mainimport ("fmt""math"
)// ==========================================================
// 步骤一:先实现基础模块 MinStack
// 这是我们用来构建 MinQueue 的“零件”
// 它的实现与你最初的 Go 版本思路完全一致
// ==========================================================type MinStack struct {stack []int // 存储实际数据的栈minStack []int // 辅助栈,minStack[i] 表示 stack[0...i] 的最小值
}// NewMinStack 创建一个新的最小栈实例
func NewMinStack() *MinStack {return &MinStack{stack: []int{},// 初始化一个“哨兵”值,方便比较,避免处理空栈的边界情况minStack: []int{math.MaxInt64},}
}// Push 将元素推入栈中
func (s *MinStack) Push(val int) {s.stack = append(s.stack, val)// 获取前一个状态的最小值lastMin := s.minStack[len(s.minStack)-1]// 新的最小值是「新元素」和「前一个状态最小值」中的较小者s.minStack = append(s.minStack, min(lastMin, val))
}// Pop 从栈中弹出一个元素
func (s *MinStack) Pop() int {if s.IsEmpty() {// 在实际工程中,这里应该返回 error 或者触发 panicpanic("stack is empty")}val := s.stack[len(s.stack)-1]s.stack = s.stack[:len(s.stack)-1]s.minStack = s.minStack[:len(s.minStack)-1]return val
}// Top 获取栈顶元素
func (s *MinStack) Top() int {if s.IsEmpty() {panic("stack is empty")}return s.stack[len(s.stack)-1]
}// GetMin 在 O(1) 时间内获取栈中最小元素
func (s *MinStack) GetMin() int {return s.minStack[len(s.minStack)-1]
}// IsEmpty 检查栈是否为空
func (s *MinStack) IsEmpty() bool {return len(s.stack) == 0
}// ==========================================================
// 步骤二:使用两个 MinStack 实现 MinQueue
// ==========================================================type MinQueue struct {inStack *MinStack // 负责处理入队 (Push)outStack *MinStack // 负责处理出队 (Pop)
}// Constructor 创建一个新的最小队列实例
func Constructor() *MinQueue {return &MinQueue{inStack: NewMinStack(),outStack: NewMinStack(),}
}// Push 将元素推入队列尾部
func (q *MinQueue) Push(val int) {// 所有新元素都直接进入 inStackq.inStack.Push(val)
}// transfer 是核心辅助函数,负责在需要时将元素从 inStack "倾倒" 到 outStack
func (q *MinQueue) transfer() {// 只有当 outStack 为空时,才需要倾倒// 如果 outStack 不为空,说明队头元素还在,不能打乱顺序if q.outStack.IsEmpty() {// 将 inStack 的所有元素逐个弹出,并压入 outStack// 这个过程会自动反转元素的顺序,并正确地构建 outStack 的最小值历史for !q.inStack.IsEmpty() {q.outStack.Push(q.inStack.Pop())}}
}// Pop 从队列头部移除元素
func (q *MinQueue) Pop() int {// 在执行出队操作前,先调用 transfer 确保 outStack 中有元素(如果可能的话)q.transfer()if q.outStack.IsEmpty() {panic("queue is empty")}return q.outStack.Pop()
}// Peek 获取队列头部的元素,但不移除
func (q *MinQueue) Peek() int {q.transfer()if q.outStack.IsEmpty() {panic("queue is empty")}return q.outStack.Top()
}// GetMin 在 O(1) 时间内获取队列中最小元素
func (q *MinQueue) GetMin() int {// 队列的全部元素分散在两个栈中// 整个队列的最小值就是这两个栈各自最小值的较小者minIn := q.inStack.GetMin()minOut := q.outStack.GetMin()return min(minIn, minOut)
}// IsEmpty 检查队列是否为空
func (q *MinQueue) IsEmpty() bool {return q.inStack.IsEmpty() && q.outStack.IsEmpty()
}// min 是一个辅助函数,返回两个整数中的较小者
func min(a, b int) int {if a < b {return a}return b
}// ==========================================================
// 主函数:用法示例
// ==========================================================func main() {mq := Constructor()fmt.Println("Push 5, GetMin:", func() int { mq.Push(5); return mq.GetMin() }()) // 输出 5fmt.Println("Push 2, GetMin:", func() int { mq.Push(2); return mq.GetMin() }()) // 输出 2fmt.Println("Push 8, GetMin:", func() int { mq.Push(8); return mq.GetMin() }()) // 输出 2fmt.Println("--------------------")fmt.Println("Pop:", mq.Pop(), " GetMin:", mq.GetMin()) // Pop 5, 剩下 [2, 8], 最小值为 2fmt.Println("Push 1, GetMin:", func() int { mq.Push(1); return mq.GetMin() }()) // Push 1, 剩下 [2, 8, 1], 最小值为 1fmt.Println("Pop:", mq.Pop(), " GetMin:", mq.GetMin()) // Pop 2, 剩下 [8, 1], 最小值为 1fmt.Println("Pop:", mq.Pop(), " GetMin:", mq.GetMin()) // Pop 8, 剩下 [1], 最小值为 1
}