📗difficulty: Easy
🎯Tags:
定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的 min 函数在该栈中,调用 min、push 及 pop 的时间复杂度都是 O(1)
。
样例 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.
提示:
- 各函数的调用总次数不超过 20000 次
注意:
- 本题与主站 155 题相同:https://leetcode-cn.com/problems/min-stack/
面对抽象的操作,使用一个具体的例子来分析题目,再构建操作模型,是一个很好的手段。
如果有一个栈来模拟真实的操作,那么实现 push
、peek
、top
的操作非常简单,因为这就是栈的操作。需要考虑的地方在 getMin
操作上。
可以设置一个数据结构来存放最小值,每次可以在 O(1) 时间内获取到最小值。如果使用栈来操作,复杂度是可以满足的。但是如何去界定存取逻辑呢?
- 如果一个数据要被压入 数据栈
stackValue
,它的值比当前的getMin
要小,那么就把这个数字同步压入 辅助栈stackMin
中去,否则不压入。 - 如果一个数据要被弹出 数据栈
stackValue
,那么就看下这个数是不是和 辅助栈stackMin
的栈顶一样,如果一样,同步弹出,否则会造成“getMin
的最小值不在 数据栈stackValue
中的错误”。
至此,将情况分为了 2 类,覆盖率全部的可能性,可以编写代码了。
虽然可以使用内建的类实现,但是面试官更加希望面试者自己手写一个栈。
可以选择使用数组实现,也可以选择 链表实现栈。
使用数组,需要考虑栈的扩容操作;使用链表的话,选择单链表,需要一点点的小技巧。
请在本题尝试使用 自己手写 的方法,完成 面试题 30。
使用 Deque
和 LinkedList
来实现 Stack
的操作。在没有特殊要求的情况下,优先选择内建类。
public class MinStack {
/**
* initialize your data structure here.
*/
private Deque<Integer> stackValue;
private Deque<Integer> stackMin;
public MinStack() {
this.stackValue = new LinkedList<>();
this.stackMin = new LinkedList<>();
}
public void push(int x) {
if (this.stackValue.isEmpty() || this.stackMin.peekLast() >= x) {
this.stackMin.offerLast(x);
}
this.stackValue.offerLast(x);
}
public void pop() {
if (this.stackValue.peekLast().equals(this.stackMin.peekLast())) {
this.stackMin.pollLast();
}
this.stackValue.pollLast();
}
public int top() {
return this.stackValue.peekLast();
}
public int min() {
return this.stackMin.peekLast();
}
}
- 时间复杂度:
O(n)
。 - 空间复杂度:
O(2n)
。
这里使用单向链表来实现一个栈,这里需要一点点的技巧,对链表的插入和删除操作均在链表头进行,可以将复杂度下降到 O(1)
,符合题目要求。
转而同时考察了 链表的 删改操作。对于链表的操作,需要格外注意空指针的问题,需要做到精准判断。
/**
* 定义 链表 节点 的结构
*/
class StackFrame {
StackFrame next;
int val;
public StackFrame(int val) {
this.val = val;
}
public StackFrame() {
}
}
/**
* 使用单链表实现栈
* 插入和删除操作在 单链表 的头部进行,复杂度为 O(1)
*/
class MyLinkedListStack {
StackFrame top;
public MyLinkedListStack() {
this.top = new StackFrame();
}
public int peek() {
return this.top.val;
}
public void push(int val) {
StackFrame newStackFrame = new StackFrame(val);
newStackFrame.next = this.top; // 即使 此时 栈为空,也不会有异常
top = newStackFrame;
}
public void pop() {
// if (top == null) {
// System.out.println("栈空,操作失败");
// return;
// }
// 题目已经给定,在不空的情况下调用 pop() 方法
this.top = this.top.next;
}
// 判定是否为空,这里需要判断的指针是 top.next
// 初始化时,top.next 和 top 均为空
// 所以判断是否为空需要判断 top.next 是否为空
public boolean isEmpty() {
return top.next == null;
}
}
public class MyMinStack {
private MyLinkedListStack stackValue;
private MyLinkedListStack stackMin;
public MyMinStack() {
this.stackValue = new MyLinkedListStack();
this.stackMin = new MyLinkedListStack();
}
public void push(int x) {
if (this.stackValue.isEmpty() || this.stackMin.peek() >= x) {
this.stackMin.push(x);
}
this.stackValue.push(x);
}
public void pop() {
if (this.stackValue.peek() == this.stackMin.peek()) {
this.stackMin.pop();
}
this.stackValue.pop();
}
public int top() {
return this.stackValue.peek();
}
public int min() {
return this.stackMin.peek();
}
public static void main(String[] args) {
MyMinStack MyMinStack = new MyMinStack();
MyMinStack.push(2);
MyMinStack.push(0);
MyMinStack.push(3);
MyMinStack.push(0);
System.out.println(MyMinStack.min());
MyMinStack.pop();
System.out.println(MyMinStack.top());
}
}
- 时间复杂度:
O(n)
。 - 空间复杂度:
O(2n)
。
顺序栈需要考虑到数组的扩容操作,其优点在于节约内存。
一般情况下,顺序栈的操作复杂度都是 O(1) ,但是在扩容的时候,需要涉及到数组的搬迁,其复杂度会上升到 O(n),但是只有那么一次,平均下来,其复杂度也是 O(1)。
class ArrayStack {
private int[] items;
private int size = 0;
private int usedSize = 0;
public ArrayStack(int size) {
this.size = size;
this.items = new int[size];
}
public ArrayStack() {
this.size = 8;
this.items = new int[8];
}
// 支持动态扩容的 顺序栈
public void push(int val) {
if (this.usedSize < this.size) {
this.items[usedSize] = val;
usedSize++;
} else {
int[] newItems = new int[this.size * 2];
if (this.size >= 0) System.arraycopy(this.items, 0, newItems, 0, this.size);
this.size = newItems.length;
newItems[usedSize++] = val;
this.items = newItems;
}
}
public void pop() {
if (this.usedSize == 0) {
System.out.println("栈空,执行操作失败");
return;
}
this.usedSize--;
}
public int peek() {
if (this.usedSize != 0) {
return this.items[usedSize - 1];
}
return -1;
}
public boolean isEmpty() {
return this.usedSize == 0;
}
}
public class MyMinArrayStack {
private ArrayStack stackValue;
private ArrayStack stackMin;
public MyMinArrayStack() {
this.stackValue = new ArrayStack();
this.stackMin = new ArrayStack();
}
public void push(int x) {
if (this.stackValue.isEmpty() || this.stackMin.peek() >= x) {
this.stackMin.push(x);
}
this.stackValue.push(x);
}
public void pop() {
if (this.stackValue.peek() == this.stackMin.peek()) {
this.stackMin.pop();
}
this.stackValue.pop();
}
public int top() {
return this.stackValue.peek();
}
public int getMin() {
return this.stackMin.peek();
}
public static void main(String[] args) {
MyMinArrayStack myMinArrayStack = new MyMinArrayStack();
myMinArrayStack.push(-2);
myMinArrayStack.push(0);
myMinArrayStack.push(3);
System.out.println(myMinArrayStack.getMin());
myMinArrayStack.pop();
System.out.println(myMinArrayStack.top());
System.out.println(myMinArrayStack.getMin());
}
}
- 时间复杂度:
O(n)
。使用 平均复杂度 来衡量 顺序栈 的时间复杂度的情况下。 - 空间复杂度:
O(2n)
。
可以把这道题看成是一道普通的关于栈的题目,也可以当作是一道关于栈的设计题,需要手工实现自己的栈。
自己实现,考察了对 数组 和 链表 的熟悉程度,和对栈的掌握情况,是一个更加可能的考察方向。