题目
设计一个Stack,实现如下操作:
- push(), 入栈
- pop(), 出栈
- top(), 返回栈顶元素
- getMin(), 返回栈中的最小元素
注意上面的操作,都要在常数时间完成。
分析
Leetcode上面的原题,思路并不难想,但是AC还是有难度的,尤其是用Java,如果能够确保不出现MLE?我们先逐步分析一下。
首先,第一个思路,最容易想到,定义一个新的栈,栈的每一个元素包含两个数,一个当前数,一个入栈到目前为止的最小数。有如下的代码示例:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
 | class Element {     public int x;     public int min;     public Element(int x, int min) {         super();         this.x = x;         this.min = min;     } } public class MinStackSlow {          private LinkedList<Element> stack = new LinkedList<Element>();          public void push(int x) {         int min = x;         if (!stack.isEmpty() && stack.getFirst().min < min)              min = stack.getFirst().min;         stack.addFirst(new Element(x, min));     }     public void pop() {         stack.removeFirst();     }     public int top() {         Element el = stack.getFirst();         return el.x;     }     public int getMin() {         return stack.getFirst().min;     } }
 | 
代码很简单,但是MLE,内存超了。。。很不幸。那么如何优化呢?其实我们自己走一走这个过程,就会发现,在入栈出栈,以及getMin的过程中,有一些值,是不会出现在getMin中的。有一些min的数都是一样的。
那么我们浪费了空间来存储这些一样的min值,显然是不可以的。我们做如下的优化,用另外一个栈来保存最小值,每一个栈元素除了包括最小值之外,还有一个频率。这样我们压缩了存储最小值的栈。比之前的栈要小很多。
示例代码如下:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
 | class MinElement {     public int min;     public int freq;     public MinElement(int min, int freq) {         this.min = min;         this.freq = freq;     } } public class MinStack {          private LinkedList<Integer> stack = new LinkedList<Integer>();     private LinkedList<MinElement> min = new LinkedList<MinElement>();          public void push(int x) {         if (min.isEmpty()) {             min.addFirst(new MinElement(x, 1));         } else {             if (x == min.getFirst().min) {                 min.getFirst().freq += 1;             } else if (x <  min.getFirst().min) {                 min.addFirst(new MinElement(x, 1));             }         }         stack.addFirst(x);     }     public void pop() {         int top = stack.removeFirst();         if (top == min.getFirst().min) {             if (min.getFirst().freq == 1) {                 min.removeFirst();             } else {                 min.getFirst().freq -= 1;             }         }     }     public int top() {         return stack.getFirst();     }     public int getMin() {         return min.getFirst().min;     } }
 | 
提交之后,你会发现,代码还是有问题,人就是内存不过关,还能怎么优化呢?难道是LinkedList的问题?实际上,还真是这个问题。Java中LinkedList是一个双向的链表,实际上我们实现一个栈,只需要单向的。我们自己实现栈试试。代码比较糙,有些实现也不合规范。
但意思没问题,如下:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
 | package leetcode.min.stack; class IntNode {     public int x;     public IntNode next;     public IntNode(int x, IntNode next) {         this.x = x;         this.next = next;     } } class IntStack {     private IntNode head;     public void push(int x) {         if (null == head) {             head = new IntNode(x, null);         } else {             IntNode newHead = new IntNode(x, head);             head = newHead;         }     }     public void pop() {         head = head.next;     }     public int top() {         return head.x;     } } class PairNode {     public int min;     public int freq;     public PairNode next;     public PairNode(int min, int freq, PairNode next) {         super();         this.min = min;         this.freq = freq;         this.next = next;     } } class PairStack {     private PairNode head;     public void push(int x) {         if (null == head) {             head = new PairNode(x, 1, null);         } else {             if (x == head.min) {                 head.freq += 1;             } else if (x < head.min) {                 PairNode newHead = new PairNode(x, 1, head);                 head = newHead;             }         }     }     public void pop(int x) {         if (x == head.min) {             if (head.freq > 1) {                 head.freq -= 1;             } else {                 head = head.next;             }         }     }     public int top() {         return head.min;     } } public class SmallMemoryStack {     private IntStack stack = new IntStack();     private PairStack min = new PairStack();     public void push(int x) {         stack.push(x);         min.push(x);     }     public void pop() {         min.pop(stack.top());         stack.pop();     }     public int top() {         return stack.top();     }     public int getMin() {         return min.top();     } }
 | 
通过了,看来问题确实如此。这也给了我们一个提醒,在实际的编码中,需要考虑的不仅仅是功能的实现,还有效率上的考虑。我们往往优先考虑时间,大多数时间考虑时间。但,空间也是同样重要的,尤其是Java这样的语言。
【完】