
算法思想:
-
使用两个栈:
stack栈:用于正常的栈操作(存储所有压入的元素)。minStack栈:专门用来存储每次压入元素时的当前最小值。
-
push 操作:
- 每当压入一个新元素时,首先将其压入
stack栈。 - 同时,检查
minStack栈顶元素是否比当前压入的元素大或相等,如果是,则将这个元素也压入minStack,表示当前栈的最小值发生变化。 - 这样,无论何时压入一个新元素,
minStack始终保持栈的最小元素在栈顶。
- 每当压入一个新元素时,首先将其压入
-
pop 操作:
- 当从
stack栈弹出元素时,检查这个弹出的元素是否与minStack栈顶元素相等。如果相等,则表明当前最小值被弹出,需要同步从minStack中弹出。 - 如果不同,则只从
stack弹出即可。
- 当从
-
top 操作:
- 直接返回
stack栈的栈顶元素,即当前栈的顶部元素。
- 直接返回
-
getMin 操作:
- 直接返回
minStack栈顶的元素,这个栈顶元素就是当前stack中的最小值,因为我们保证了minStack始终存储着每次操作后的最小值。
- 直接返回
时间复杂度分析:
- 由于每次
push、pop、top和getMin操作都只涉及常数次的栈操作,所有操作的时间复杂度均为 O(1)。
通过这个算法,我们实现了在常数时间内(O(1))进行压栈、出栈、查询栈顶元素和查询最小元素的功能,非常高效。
class MinStack {//成员变量private Stack<Integer> stack;private Stack<Integer> minStack;public MinStack() { //构造函数stack = new Stack<>();minStack = new Stack<>();}public void push(int val) {stack.push(val);//判空条件必须先放在前面 // 必须是小于等于if(minStack.isEmpty() || val <= minStack.peek()) { minStack.push(val);}}public void pop() {//为什么用equals()而不是==?// == 比较的是引用(内存地址), equals() 比较的是值if(stack.peek().equals(minStack.peek())) {minStack.pop();}stack.pop();}public int top() {return stack.peek();}public int getMin() {return minStack.peek();}
}
注意点
为什么这行代码的判断条件中,如果使用小于而不是小于等于也会出错?
if (minStack.isEmpty() || val <= minStack.peek())
在 MinStack 这个实现中,使用 小于等于(<=) 而不是 小于(<) 的原因与维护栈中最小值的正确性有关。下面我们详细解释为什么使用 < 会导致错误,以及为什么必须使用 <=。
问题分析:
首先,来看这段代码:
if (minStack.isEmpty() || val <= minStack.peek()) {minStack.push(val);
}
1. 使用小于(<)可能导致的错误:
-
逻辑问题:如果你使用的是
<,表示只有当当前要压入的值严格小于minStack栈顶元素时,才会将该值压入minStack。这意味着相同的最小值不会被再次压入minStack。 -
错误结果:如果栈中存在多个相同的最小值,例如连续压入多个值
3,当其中一个3被弹出时,你的minStack不会弹出这个3,因为在栈顶只有一个3,但实际上栈中还剩下多个3。这样一来,minStack不再能正确跟踪最小值。举个例子:
- 假设你压入了三个元素:
[3, 3, 3],如果使用<,minStack中只会有一个3,因为只有第一个3会被压入,后面的两个相等的3不会进入minStack。 - 然后你执行三次
pop():- 第一次弹出栈顶
3,minStack弹出3。 - 第二次弹出栈顶
3,但是此时minStack已经为空(因为只存了一个3),导致获取最小值时出错。
- 第一次弹出栈顶
- 假设你压入了三个元素:
2. 为什么要使用小于等于(<=):
-
逻辑保证:使用
<=可以保证每次压入的值如果等于minStack栈顶元素,也会被压入minStack,这样可以确保minStack里存储了所有最小值的拷贝。当多个最小值存在时,minStack里会有多个相同的最小值。 -
正确性保证:这样做的好处是,当你从主栈
stack弹出最小值时,minStack中也会同步弹出该最小值,确保栈中仍然能正确返回当前的最小值。
示例说明:
假设你要压入 [3, 3, 2, 1, 1] 这些值。
-
当你使用
<=时:minStack会压入:[3, 3, 2, 1, 1],这样你无论弹出多少个最小值,minStack都能同步更新,始终保持栈中的最小值正确。
-
如果你使用
<:minStack只会压入:[3, 2, 1],当你弹出两个1时,minStack只会弹出一个1,导致getMin()在弹出第二个1后出错。
总结:
- 使用
<=可以保证当你压入多个相同的最小值时,minStack会存储每一个相等的最小值,这样在弹出元素时能够正确同步更新最小值。 - 如果使用
<,那么相等的最小值不会被重复压入,这会导致在弹出多个相同的最小值时,minStack无法正确更新,进而导致错误的最小值返回。
因此,为了保持最小栈逻辑的正确性,必须使用 <= 来确保每个相等的最小值都被记录下来。
