题目:定义栈的数据结构,请在该类型中实现一个能够得到栈/队列的最小元素的min函数。在该栈/队列中,调用min、入栈(入队列)及出栈(出队列)函数的时间复杂度都是O(1)。
1. 包含min函数的栈
看到这个问题,第一反应是创建一个成员变量保存栈中当前的最小元素。每次压入一个新元素进栈时,如果该元素比当前最小的元素还要小,则更新最小元素。采用这种思路,元素入栈的时候没有问题,但出栈时如果最小的元素被弹出栈了,就无法得到下一个最小的元素。如图,元素入栈出栈过程如下,当元素3出栈时,无法确定栈中当前最小元素4。
元素入栈出栈过程分析
分析到这里,我们发现仅仅添加一个成员变量存放最小元素时不够的,也就是当最小元素被弹出栈的时候,我们希望能够得到次小元素。因此,在压入这个最小元素之前,需要把次小元素保存起来,于是采用一个“辅助栈”保存历史最小元素。还是以上面的入栈出栈为例,多了一个辅助栈,当元素3出栈时,依然可以的到栈的当前最小元素。
元素入栈出栈过程分析
算法的代码实现如下:
1 import java.util.LinkedList; 2 import java.util.Stack; 3 4 public class d30_MinInStack { 5 public static void main(String[] args) { 6 // 测试包含min函数的栈 7 StackWithMin<Integer> stack = new StackWithMin<>(); 8 stack.push(3); 9 stack.push(4); 10 stack.push(2); 11 stack.pop(); // 当前最小元素出栈,则minStack最小元素也要出栈 12 stack.push(0); 13 stack.push(-1); 14 System.out.println(stack.dataStack); // [3, 4, 0, -1] 15 System.out.println(stack.minStack); // [3, 0, -1] 16 System.out.println(stack.min()); // -1 17 } 18 19 /* 20 * 包含min函数的栈 21 */ 22 static class StackWithMin<T extends Comparable<T>> { 23 private Stack<T> dataStack; // 记录实际元素的栈 24 private Stack<T> minStack; // 记录最小元素的栈 25 26 public StackWithMin() { 27 this.dataStack = new Stack<>(); 28 this.minStack = new Stack<>(); 29 } 30 31 /* 32 * 元素入栈 33 */ 34 public void push(T data) { 35 if (data == null) { 36 throw new RuntimeException("cannot push null"); 37 } 38 if (dataStack.isEmpty()) { 39 dataStack.push(data); 40 minStack.push(data); 41 } else { 42 dataStack.push(data); 43 // 记录最小栈的当前最小元素 44 T curMin = minStack.peek(); 45 // 新入栈函数小于curMin 46 if (data.compareTo(curMin) < 0) { 47 minStack.push(dataStack.peek()); 48 } 49 } 50 } 51 52 /* 53 * 元素出栈 54 */ 55 public T pop() { 56 if (dataStack.isEmpty()) { 57 throw new RuntimeException("dataStack is empty!"); 58 } 59 // 如果出栈元素为dataStack栈的当前最小元素,minStack的栈顶元素出栈 60 if (dataStack.peek() == minStack.peek()) { 61 minStack.pop(); 62 } 63 return dataStack.pop(); 64 } 65 66 /* 67 * 取栈中最小元素 68 */ 69 public T min() { 70 if (minStack.isEmpty()) { 71 throw new RuntimeException("minStack is empty!"); 72 } 73 return minStack.peek(); 74 } 75 } 76 }
2. 包含min函数的队列
有了上面对栈的分析作为基础,实现包含min函数的队列就比较容易了。实现思路还是创建“辅助队列”,但与“辅助栈”不同的是,“辅助队列”并不是保存队列中历史最小元素。如果不是最小元素入队列,则“辅助队列”以递减的方式保存元素;如果是最小元素入队列,则“辅助队列”将其他元素出队,只保存该最小元素。具体的过程如下图所示:
元素入队出队过程分析
算法实现代码如下:
1 import java.util.LinkedList; 2 import java.util.Stack; 3 4 public class d30_MinInStack { 5 6 public static void main(String[] args) { 7 // 测试包含min函数的队列 8 QueueWithMin<Integer> queue = new QueueWithMin<>(); 9 queue.enqueue(3); 10 queue.enqueue(4); 11 queue.enqueue(7); 12 System.out.println(queue.min()); 13 queue.enqueue(5); 14 System.out.println(); 15 System.out.println(queue.min()); 16 } 17 18 /* 19 * 包含min函数的队列 20 */ 21 public static class QueueWithMin<T extends Comparable<T>> { 22 private LinkedList<T> dataQueue; 23 private LinkedList<T> minQueue; 24 25 public QueueWithMin() { 26 dataQueue = new LinkedList<>(); 27 minQueue = new LinkedList<>(); 28 } 29 30 // 元素入队 31 public void enqueue(T data) { 32 if (data == null) { 33 throw new RuntimeException("cannot enqueue null"); 34 } 35 dataQueue.offer(data); 36 // 保证minQueue队列的元素递减 37 // 若data < 队头元素,则minQueue队头元素出队 38 while (!minQueue.isEmpty() && (data.compareTo(minQueue.peek()) < 0)) { 39 minQueue.poll(); 40 } 41 // 若data < 队尾元素,则minQueue队尾元素出队 42 while(!minQueue.isEmpty() && (data.compareTo(minQueue.peekLast()) < 0)) { 43 minQueue.poll(); 44 } 45 minQueue.offer(data); 46 } 47 48 // 元素出队 49 public T dequeue() { 50 if (dataQueue.isEmpty()) { 51 throw new RuntimeException("dataQueue is empty!"); 52 } 53 // dataQueue出队元素与minQueue队头元素相等,则minQueue队头元素出队 54 if (dataQueue.peek() == minQueue.peek()) { 55 minQueue.poll(); 56 } 57 return dataQueue.poll(); 58 } 59 60 // 取队列最小元素 61 public T min() { 62 if (minQueue.isEmpty()) { 63 throw new RuntimeException("minQueue is empty!"); 64 } 65 return minQueue.peek(); 66 } 67 } 69 }