题目

​链接​

【LeetCode】115.最小栈(辅助栈,java实现)_出栈

解答

这道题的思想很简单:“以空间换时间”,使用辅助栈是常见的做法。

思路分析:

在代码实现的时候有两种方式:

1、辅助栈和数据栈同步

特点:编码简单,不用考虑一些边界情况,就有一点不好:辅助栈可能会存一些“不必要”的元素。

2、辅助栈和数据栈不同步

特点:由“辅助栈和数据栈同步”的思想,我们知道,当数据栈进来的数越来越大的时候,我们要在辅助栈顶放置和当前辅助栈顶一样的元素,这样做有点“浪费”。基于这一点,我们做一些“优化”,但是在编码上就要注意一些边界条件。

(1)辅助栈为空的时候,必须放入新进来的数;

(2)新来的数小于或者等于辅助栈栈顶元素的时候,才放入,特别注意这里“等于”要考虑进去,因为出栈的时候,连续的、相等的并且是最小值的元素要同步出栈

(3)出栈的时候,辅助栈的栈顶元素等于数据栈的栈顶元素,才出栈。

总结一下:出栈时,最小值出栈才同步;入栈时,最小值入栈才同步

对比:个人觉得“同步栈”的方式更好一些,因为思路清楚,因为所有操作都同步进行,所以调试代码、定位问题也简单。“不同步栈”,虽然减少了一些空间,但是在“出栈”、“入栈”的时候还要做判断,也有性能上的消耗。

方法一:辅助栈和数据栈同步

参考代码 1

import java.util.Stack;

public class MinStack {

// 数据栈
private Stack<Integer> data;
// 辅助栈
private Stack<Integer> helper;

/**
* initialize your data structure here.
*/
public MinStack() {
data = new Stack<>();
helper = new Stack<>();
}

// 思路 1:数据栈和辅助栈在任何时候都同步

public void push(int x) {
// 数据栈和辅助栈一定会增加元素
data.add(x);
if (helper.isEmpty() || helper.peek() >= x) {
helper.add(x);
} else {
helper.add(helper.peek());
}
}

public void pop() {
// 两个栈都得 pop
if (!data.isEmpty()) {
helper.pop();
data.pop();
}
}

public int top() {
if(!data.isEmpty()){
return data.peek();
}
throw new RuntimeException("栈中元素为空,此操作非法");
}

public int getMin() {
if(!helper.isEmpty()){
return helper.peek();
}
throw new RuntimeException("栈中元素为空,此操作非法");
}
}


复杂度分析

  • 时间复杂度:O(1),“出栈”、“入栈”、“查看栈顶元素”的操作不论数据规模多大,都只是有限个步骤,因此时间复杂度是:O(1)O(1)。
  • 空间复杂度:O(N),这里 N 是读出的数据的个数。

方法二:辅助栈和数据栈不同步

  • 借用一个辅助栈​​min_stack​​,用于存获取​​stack​​中最小值。
  • 算法流程:
  • push()方法: 每当​​push()​​新值进来时,如果 小于等于​min_stack​​栈顶值,则一起​​push()​​到​​min_stack​​,即更新了栈顶最小值;
  • pop()方法: 判断将​​pop()​​出去的元素值是否是​​min_stack​​栈顶元素值(即最小值),如果是则将​​min_stack​​栈顶元素一起​​pop()​​,这样可以保证​​min_stack​​栈顶元素始终是​​stack​​中的最小值。
  • getMin()方法: 返回​​min_stack​​栈顶即可。
  • min_stack作用分析:
  • ​min_stack​​等价于遍历​​stack​​所有元素,把升序的数字都删除掉,留下一个从栈底到栈顶降序的栈。
  • 相当于给​​stack​​中的降序元素做了标记,每当​​pop()​​这些降序元素,​​min_stack​​会将相应的栈顶元素​​pop()​​出去,保证其栈顶元素始终是​​stack​​中的最小元素。

【LeetCode】115.最小栈(辅助栈,java实现)_时间复杂度_02

参考代码 2

import java.util.Stack;

public class MinStack {

// 数据栈
private Stack<Integer> data;
// 辅助栈
private Stack<Integer> helper;

/**
* initialize your data structure here.
*/
public MinStack() {
data = new Stack<>();
helper = new Stack<>();
}

// 思路 2:辅助栈和数据栈不同步
// 关键 1:辅助栈的元素空的时候,必须放入新进来的数
// 关键 2:新来的数小于或者等于辅助栈栈顶元素的时候,才放入(特别注意这里等于要考虑进去)
// 关键 3:出栈的时候,辅助栈的栈顶元素等于数据栈的栈顶元素,才出栈,即"出栈保持同步"就可以了

public void push(int x) {
// 辅助栈在必要的时候才增加
data.add(x);
// 关键 1 和 关键 2
if (helper.isEmpty() || helper.peek() >= x) {
helper.add(x);
}
}

public void pop() {
// 关键 3:data 一定得 pop()
if (!data.isEmpty()) {
// 注意:声明成 int 类型,这里完成了自动拆箱,从 Integer 转成了 int,因此下面的比较可以使用 "==" 运算符

// 如果把 top 变量声明成 Integer 类型,下面的比较就得使用 equals 方法
int top = data.pop();
if(top == helper.peek()){
helper.pop();
}
}
}

public int top() {
if(!data.isEmpty()){
return data.peek();
}
throw new RuntimeException("栈中元素为空,此操作非法");
}

public int getMin() {
if(!helper.isEmpty()){
return helper.peek();
}
throw new RuntimeException("栈中元素为空,此操作非法");
}

}


参考代码3:

class MinStack {
private Stack<Integer> stack;
private Stack<Integer> min_stack;
public MinStack() {
stack = new Stack<>();
min_stack = new Stack<>();
}
public void push(int x) {
stack.push(x);
if(min_stack.isEmpty() || x <= min_stack.peek())
min_stack.push(x);
}
public void pop() {
if(stack.pop().equals(min_stack.peek()))
min_stack.pop();
}
public int top() {
return stack.peek();
}
public int getMin() {
return min_stack.peek();
}
}


复杂度分析

  • 时间复杂度:O(1),“出栈”、“入栈”、“查看栈顶元素”的操作不论数据规模多大,都只有有限个步骤,因此时间复杂度是:O(1)。
  • 空间复杂度:O(N),这里 N是读出的数据的个数。