题目: 定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的 min 函数在该栈中,调用 min、push 及 pop 的时间复杂度都是 O(1)。
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.min(); --> 返回 -3
minStack.pop();
minStack.top(); --> 返回 0
minStack.min(); --> 返回 -2
难点
- push 和 pop 的操作时间复杂度是 O(1), min 函数如果使用排序或查找实现, 时间复杂度是
O(n), 无法完成题目要求O(1) - 定义一个 minValue 保存最小值, 每次 push 时, 和 minValue 比较, 保证 minValue 一直是最小元素, 调用 min 函数的时候返回 minValue 的值。 但是如果 pop 出去的值刚好就是最小值, 再次调用 min 的时候就无法知道次最小值是多少了。
思路
可以通过两个栈来实现, 一个主栈用来存储数据结构, 一个辅助栈用来存储最小值。
- 当主栈元素入栈时, 辅栈存储栈中元素的最小值
- 当主栈元素出栈时, 辅栈元素也出栈
- 最小值就是辅栈元素的顶部值
- 栈顶值就是主栈元素的顶部值
代码
class Stack {
constructor() {
this.arr = []
}
add(value) {
this.arr.push(value)
}
pop() {
return this.arr.pop()
}
top() {
const len = this.arr.length;
if(len != 0) {
return this.arr[len - 1]
}
}
empty() {
return this.arr.length === 0
}
}
class MinStack {
constructor() {
this.A = new Stack() // 主栈
this.B = new Stack() // 辅栈
}
push(value) {
this.A.add(value)
if(this.B.empty()) {
this.B.add(value)
}else{
this.B.add(value < this.B.top() ? value : this.B.top())
}
}
min() {
return this.B.top() ///返回栈最小值
}
pop() {
this.B.pop()
return this.A.pop()
}
top() {
return this.A.top() //返回栈顶值
}
}
const minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
console.log(minStack.min()); //返回 -3
minStack.pop();
console.log(minStack.top()); //返回 0
console.log(minStack.min()); //返回 -2
总结
本篇我们学习了如何实现一个自带 min 函数的栈, min 函数的时间复杂度为 O(1)
利用主栈和辅栈实现 min, 主栈存储数据结构,辅栈存储最小值,主栈和辅栈同时入栈和出栈,保持栈的同步,主栈的 top 是栈的顶值, 辅栈的 top 是栈的最小值。
思考
通过辅栈的方式, min 的时间复杂度为 O(1) 达到了解题要求, 但是辅栈内部有很多连续重复的元素,会额外占用一份主站的空间, 辅栈的存储是否可以优化呢?
可以
- 入栈时,辅栈只存储比辅栈栈顶小的值
- 出栈时,主栈 pop, 如果最小值被 pop了, 辅栈也要和主栈同步, 把最小值 pop 了。
class MinStack {
push(value) {
this.A.add(value)
if(this.B.empty() || value <= this.B.top()) {
this.B.add(value)
}
}
pop() {
if(this.A.pop() === this.B.top()) {
this.B.pop()
}
}
}