题目描述(简单难度)

设计数据结构的题,设计一个栈,除了栈特有的功能,入栈、出栈、查看栈顶元素,还需要增加一个功能,得到当前栈里边最小的元素。

解法一

要实现一个 ,那么我们还能用 java 自带的 stack 吗?也不用纠结,这道题的关键其实是实现「得到最小值这个功能」,所以为了代码简洁些,我们就直接使用系统自带的 stack 了。

这道题最直接的解法就是我们可以用两个栈,一个栈去保存正常的入栈出栈的值,另一个栈去存最小值,也就是用栈顶保存当前所有元素的最小值。存最小值的栈的具体操作流程如下:

将第一个元素入栈。

新加入的元素如果大于栈顶元素,那么新加入的元素就不处理。

新加入的元素如果小于等于栈顶元素,那么就将新元素入栈。

出栈元素不等于栈顶元素,不操作。

出栈元素等于栈顶元素,那么就将栈顶元素出栈。

举个例子。

代码的话就很好写了。

  1. class MinStack {
  2. /** initialize your data structure here. */
  3. private Stack<Integer> stack;
  4. private Stack<Integer> minStack;
  5. public MinStack() {
  6. stack = new Stack<>();
  7. minStack = new Stack<>();
  8. }
  9. public void push(int x) {
  10. stack.push(x);
  11. if (!minStack.isEmpty()) {
  12. int top = minStack.peek();
  13. //小于的时候才入栈
  14. if (x <= top) {
  15. minStack.push(x);
  16. }
  17. }else{
  18. minStack.push(x);
  19. }
  20. }
  21. public void pop() {
  22. int pop = stack.pop();
  23. int top = minStack.peek();
  24. //等于的时候再出栈
  25. if (pop == top) {
  26. minStack.pop();
  27. }
  28. }
  29. public int top() {
  30. return stack.peek();
  31. }
  32. public int getMin() {
  33. return minStack.peek();
  34. }
  35. }

解法二

解法一中用了两个栈去实现,那么我们能不能用一个栈去实现呢?

参考了 。

再看一下上边的例子。

  1. 入栈 3
  2. | | min = 3
  3. | |
  4. |_3_|
  5. stack
  6. 入栈 5
  7. | | min = 3
  8. | 5 |
  9. |_3_|
  10. stack
  11. 入栈 2
  12. | 2 | min = 2?
  13. | 5 |
  14. stack

如果只用一个变量就会遇到一个问题,如果把 min 更新为 2,那么之前的最小值 3 就丢失了。

怎么把 3 保存起来呢?把它在 2 之前压入栈中即可。

上边的最后一个状态,当出栈元素是最小元素我们该如何处理呢?

我们只需要把 2 出栈,然后再出栈一次,把 3 赋值给 即可。

  1. 出栈 2
  2. | | min = 3
  3. | 5 |
  4. |_3_|
  5. stack

通过上边的方式,我们就只需要一个栈了。当有更小的值来的时候,我们只需要把之前的最小值入栈,当前更小的值再入栈即可。当这个最小值要出栈的时候,下一个值便是之前的最小值了。

  1. class MinStack {
  2. int min = Integer.MAX_VALUE;
  3. Stack<Integer> stack = new Stack<Integer>();
  4. public void push(int x) {
  5. //当前值更小
  6. if(x <= min){
  7. //将之前的最小值保存
  8. stack.push(min);
  9. //更新最小值
  10. min=x;
  11. }
  12. stack.push(x);
  13. }
  14. public void pop() {
  15. //如果弹出的值是最小值,那么将下一个元素更新为最小值
  16. if(stack.pop() == min) {
  17. min=stack.pop();
  18. }
  19. }
  20. public int top() {
  21. return stack.peek();
  22. }
  23. public int getMin() {
  24. return min;
  25. }
  26. }

解法三

参考 ,再分享利用一个栈的另一种思路。

通过解法二的分析,我们关键要解决的问题就是当有新的更小值的时候,之前的最小值该怎么办?

解法二中通过把之前的最小值入栈解决问题。

这里的话,用了另一种思路。同样是用一个 min 变量保存最小值。只不过栈里边我们不去保存原来的值,而是去存储入栈的值和最小值的差值。然后得到之前的最小值的话,我们就可以通过 min 值和栈顶元素得到,举个例子。

再理一下上边的思路,我们每次存入的是 原来值 - 当前最小值

当原来值大于等于当前最小值的时候,我们存入的肯定就是非负数,所以出栈的时候就是 栈中的值 + 当前最小值

当后续如果出栈元素是负数的时候,那么要出栈的元素其实就是 min。此外之前的 min 值,我们可以通过栈顶的值和当前 min 值进行还原,就是用 min 减去栈顶元素即可。

  1. public class MinStack {
  2. long min;
  3. Stack<Long> stack;
  4. public MinStack(){
  5. stack=new Stack<>();
  6. }
  7. public void push(int x) {
  8. if (stack.isEmpty()) {
  9. min = x;
  10. stack.push(x - min);
  11. } else {
  12. stack.push(x - min);
  13. if (x < min){
  14. min = x; // 更新最小值
  15. }
  16. }
  17. }
  18. public void pop() {
  19. if (stack.isEmpty())
  20. return;
  21. long pop = stack.pop();
  22. if (pop < 0) {
  23. min = min - pop;
  24. }
  25. }
  26. public int top() {
  27. long top = stack.peek();
  28. //负数的话,出栈的值保存在 min 中
  29. if (top < 0) {
  30. return (int) (min);
  31. //出栈元素加上最小值即可
  32. } else {
  33. return (int) (top + min);
  34. }
  35. }
  36. public int getMin() {
  37. return (int) min;
  38. }
  39. }

上边的解法的一个缺点就是由于我们保存的是差值,所以可能造成溢出,所以我们用了数据范围更大的 long 类型。

此外相对于解法二,最小值需要更新的时候,我们并没有将之前的最小值存起来,我们每次都是通过当前最小值和栈顶元素推出了之前的最小值,所以会省一些空间。

解法四

再分享一个有趣的解法,参考 。

回到最初的疑虑,我们要不要用 java 提供的 stack 。如果不用的话,可以怎么做的?

直接用一个链表即可实现栈的基本功能,那么最小值该怎么得到呢?我们可以在 Node 节点中增加一个 min 字段,这样的话每次加入一个节点的时候,我们同时只要确定它的 min 值即可。

代码很简洁,我直接把代码贴过来吧。

  1. class MinStack {
  2. class Node{
  3. int value;
  4. int min;
  5. Node next;
  6. Node(int x, int min){
  7. this.value=x;
  8. this.min=min;
  9. next = null;
  10. }
  11. }
  12. Node head;
  13. //每次加入的节点放到头部
  14. public void push(int x) {
  15. if(null==head){
  16. head = new Node(x,x);
  17. }else{
  18. //当前值和之前头结点的最小值较小的做为当前的 min
  19. Node n = new Node(x, Math.min(x,head.min));
  20. n.next=head;
  21. head=n;
  22. }
  23. }
  24. public void pop() {
  25. if(head!=null)
  26. head =head.next;
  27. }
  28. public int top() {
  29. if(head!=null)
  30. return head.value;
  31. return -1;
  32. }
  33. public int getMin() {
  34. if(null!=head)
  35. return head.min;
  36. return -1;
  37. }

虽然题目比较简单,但解法二和解法三真的让人耳目一新,一个通过存储,一个通过差值解决了「保存之前值」的问题,思路很值得借鉴。解法四更像降维打击一样,回到改底层数据结构,从而更加简洁的解决了问题。

添加好友一起进步~

如果觉得有帮助的话,可以点击 给一个 star 哦 ^^

如果想系统的学习数据结构和算法,强烈推荐一个我之前学过的课程,可以点击 这里 查看详情