LeetCode 155. 最小栈 - 思考与最优解
分析
栈是一种先进后出的数据结构,对于栈来说,如果一个元素 a 在入栈时,栈里有其它的元素 b, c, d,那么无论这个栈在之后经历了什么操作,只要 a 在栈中,b, c, d 就一定在栈中。
因此,在对栈操作过程中的任意一个时刻,只要栈顶的元素是 a,那么我们就可以确定栈里面现在的元素一定是 a, b, c, d。
那么,我们可以在每个元素 x 入栈时把当前栈(包括 {a, b,...,x})的最小值 min 存储起来。在这之后无论何时,如果栈顶元素是 x,我们就可以直接返回存储的最小值 min。
现在我们需要考虑:如何实现 “存储 min 值” 这个操作。
首先可以想到,在对某个元素入栈时,把当前主栈的最小值 push 到另外一个栈中,也就是引入一个辅助栈。并且对于主栈的每一次 push 和 pop 操作,辅助栈也会进行相应的 push 和 pop 操作,保证任意时刻,辅助栈的栈顶元素都记录着当前主栈的最小值。
更进一步,由于主栈和辅助栈的元素是一一对应的,也就是两者必然同时出现,那么可以考虑弃用辅助栈,取而代之,对于主栈的每一次入栈操作,push 一个二元组 <a, min> 。其中 a 表示当前元素,min 表示当前栈的最小值。
以上两种方法的时间复杂度为 \(O(1)\), 空间复杂度为 \(O(n)\),其中空间复杂度有没有优化的空间呢?
有,但是对于题目的要求有一定限制。
具体来说,我们可以引入一个辅助常量 min,表示当前栈的最小值。那么栈中的每个元素不能是当前元素的原始值了,而是当前元素与 min 的差值。当然,我们在对栈进行 push,pop 操作时,也需要按照一定的策略更新这个 min 值,以确保它能正确表示当前栈的最小值。
这种方法的限制就在于,因为要存储当前元素与 min 的差值,考虑极端情况下,使用 int 类型来保存可能会发生溢出,虽然这时可以考虑使用 long 类型,不过 long 类型的存储空间大小是 int 类型的二倍,还是付出了空间的代价。但是对于数据范围在 $\left [ -2^{30} + 1 , \ \ 2^{30} - 1 \right ] $ 时,这种方法的时间和空间复杂度都为 \(O(1)\)。
下面给出具体实现:
辅助栈
class MinStack {
// 两个栈:主栈和辅助栈
Stack<Integer> st1;
Stack<Integer> st2;
public MinStack() {
st1 = new Stack<>();
st2 = new Stack<>();
}
public void push(int val) {
if (st1.empty()) {
st1.push(val);
st2.push(val);
} else {
st1.push(val);
int min = st2.peek();
min = min < val ? min : val;
st2.push(min); // 辅助栈只记录当前的最小值
}
}
public void pop() {
st1.pop();
st2.pop();
}
public int top() {
return st1.peek();
}
public int getMin() {
return st2.peek();
}
}
辅助栈 pro
class MinStack {
Stack<Node> st;
// 用一个 Node 同时存储 val 和当前的 min 值
class Node {
int val;
int min;
Node(int val, int min) {
this.val = val;
this.min = min;
}
}
public MinStack() {
st = new Stack<Node>();
}
public void push(int val) {
if (st.empty()) {
Node tmp = new Node(val, val);
st.push(tmp);
} else {
Node pre = st.peek();
int min = pre.min < val ? pre.min : val;
Node tmp = new Node(val, min);
st.push(tmp);
}
}
public void pop() {
st.pop();
}
public int top() {
return st.peek().val;
}
public int getMin() {
return st.peek().min;
}
}
进阶:辅助常量
class MinStack {
Stack<Integer> st;
int min;
public MinStack() {
st = new Stack<>();
}
public void push(int val) {
if (st.empty()) {
min = val;
st.push(0);
} else {
int diff = val - min; // 可能会发生溢出
if (diff < 0) { // 差值小于零,意味着当前 val 为最小,需要更新 min 值
min = val;
}
st.push(diff);
}
}
public void pop() {
int tmp = st.pop();
if (tmp < 0) { // 如果弹出了最小值,就要记得更新 min 值
min = min - tmp;
}
}
public int top() {
return st.peek() < 0 ? min : st.peek() + min;
}
public int getMin() {
return min;
}
}

浙公网安备 33010602011771号