LeetCode 剑指 Offer 30. 包含min函数的栈
题目描述



题意分析
实现支持入栈、出栈、读取栈顶和读取当前最小值的栈。
min只查询,不删除元素;题目保证弹出和查询时栈非空,元素值允许重复。最小值不能每次通过遍历重新寻找。除了记录当前最小值,还需要在它被弹出后恢复之前的最小值,所以要保存最小值变化的历史。
解法:辅助最小栈
核心思路
[!blue]
数据栈
data保存全部元素,辅助栈mins只保存成为最小值的那些出现。压入新值时,如果辅助栈为空,或新值不大于当前最小值,就同时压入辅助栈;否则只压入数据栈。较大的新值为什么不需要记录?它压在旧最小值上面,按照栈的顺序,必须先弹出它才能弹出下面的旧最小值,所以在它还留在栈里时,不会需要它来替代那个更小值。只有更小或相等的新值会影响最小值的恢复过程。
相等值也必须分别记录。这样多个相同最小值各对应一次出现,弹出其中一个时辅助栈只减少一份,其余相同最小值仍有记录,不会过早恢复成更大的历史值。
出栈时先保存数据栈实际弹出的值。若它等于当前最小值,辅助栈也弹出一次,露出此前应恢复的最小值;否则最小元素仍在数据栈中,辅助栈保持不变。因此两个栈不要求等高,但辅助栈顶始终准确表示当前最小值。
top读取数据栈顶,min读取辅助栈顶,都不修改状态。Java 用基本类型int保存弹出值,再与辅助栈顶比较,比较的是数值而不是包装对象的引用身份。
解题步骤
- 初始化数据栈
data和辅助栈mins。- 入栈时总是压入
data;若新值不大于当前最小值,或辅助栈为空,也压入mins。- 出栈时保存弹出值,只有它等于辅助栈顶时才同步弹出
mins。- 栈顶查询读取
data,最小值查询读取mins。
代码实现
class MinStack {
private final Deque<Integer> data;
private final Deque<Integer> mins;
public MinStack() {
data = new ArrayDeque<>();
mins = new ArrayDeque<>();
}
public void push(int x) {
data.push(x);
// 相等的最小值也要保存,弹出时才能逐个对应。
if (mins.isEmpty() || x <= mins.peek()) {
mins.push(x);
}
}
public void pop() {
// 比较刚弹出的值;用 int 按数值比较,避免包装对象引用比较。
int val = data.pop();
if (val == mins.peek()) {
mins.pop();
}
}
public int top() {
return data.peek();
}
public int min() {
return mins.peek();
}
}
type MinStack struct {
data []int
mins []int
}
func Constructor() MinStack {
return MinStack{
data: make([]int, 0),
mins: make([]int, 0),
}
}
func (s *MinStack) Push(x int) {
s.data = append(s.data, x)
// 相等的最小值也要保存,弹出时才能逐个对应。
if len(s.mins) == 0 || x <= s.mins[len(s.mins)-1] {
s.mins = append(s.mins, x)
}
}
func (s *MinStack) Pop() {
n := len(s.data)
val := s.data[n-1]
s.data = s.data[:n-1]
// 只有弹出了当前最小值,辅助栈才同步弹出。
if val == s.mins[len(s.mins)-1] {
s.mins = s.mins[:len(s.mins)-1]
}
}
func (s *MinStack) Top() int {
return s.data[len(s.data)-1]
}
func (s *MinStack) Min() int {
return s.mins[len(s.mins)-1]
}
复杂度分析
- 时间复杂度:push 为摊还 $O(1)$,发生扩容的单次 push 为 $O(n)$;pop、top、min 为 $O(1)$。
- 空间复杂度:$O(n)$,n 按执行过程中的最大栈规模计;底层数组弹出元素后不一定立即缩容。
关键点总结
[!green]
- 相等也入栈,保证重复最小值有对应份数。
- 比较的是刚弹出的值,而不是弹出后的新栈顶。
- Java 用 int 接住弹出值,比较数值而不是 Integer 引用。
易错点总结
[!yellow]
- 只保存严格更小值:会遗漏重复最小值的出现次数,弹出一份后可能过早丢失仍存在的最小值。
- 每次出栈都弹出辅助栈:弹出的数据若不是最小值,辅助记录就不应改变。
- 比较弹出后的新栈顶:需要判断的是刚离开的元素,而不是现在仍留在栈中的元素。
- 查询最小值时执行弹出:查询不能破坏历史记录,必须读取栈顶。
- 只保存一个最小值变量:最小元素离开后无法直接恢复之前的状态。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 716. 最大栈 | 困难 | 同样在栈操作中维护极值,原题还支持删除最大值,需要更复杂的定位与连接。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!