第一章 栈和队列
1.1 设计一个有 getMin 功能的栈
【题目】
实现一个特殊的栈,在实现栈的基本功能的基础上,再实现返回栈中最小元素的操作。
【要求】
- pop、push、getMin 操作的时间复杂度都是 O(1)。
- 设计的栈类型可以使用现成的栈结构。
【难度】
士 ★☆☆☆
【题解】
数据结构采用两个栈:一个用来保存当前栈中的元素,记为 stackData;另一个栈用于保存每一步操作后栈中元素的最小值,记为 stackMin。实现方式有两种:
第一种方案:
- 压入数据规则:(假设当前数据为 newNum,先将其压入 stackData,然后判断 stackMin 是否为空)
- 如果为空,则 newNum 也压入 stackMin;
- 如果不为空,则比较 newNum 和 stackMin 的栈顶元素 topNum 的大小:
- 如果 newNum ≤ topNum,则 newNum 也压入 stackMin;
- 如果 newNum > topNum,则 newNum 不压入 stackMin。
- 弹出数据规则:
- 先在 stackData 中弹出栈顶元素,记为 value。然后比较其与当前 stackMin 的栈顶元素 topNum 的大小。
- 由压入规则可知,stackMin 中的元素从栈底到栈顶逐渐减小,topNum 既是 stackMin 的最小值,也是当前 stackData 中元素的最小值。因此,value 只可能大于或等于 topNum。
- 当 value = topNum,stackMin 弹出栈顶元素;
- 当 value > topNum,stackMin 不弹出栈顶元素。
- 压入操作和弹出操作是对应的。
- 查询当前栈中的最小值:
- 由压入规则和弹出规则可知,stackMin 始终记录着 stackData 中的最小值。所以,最小值即为 stackMin 的栈顶元素。
第二种方案:
- 压入数据规则:(假设当前数据为 newNum,先将其压入 stackData,然后判断 stackMin 是否为空)
- 如果为空,则 newNum 也压入 stackMin;
- 如果不为空,则比较 newNum 和 stackMin 的栈顶元素 topNum 的大小:
- 如果 newNum ≤ topNum,则 newNum 也压入 stackMin;
- 如果 newNum > topNum,则将 topNum 重复压入 stackdsadaMin。
- 弹出数据规则:
- 在 stackData 中弹出数据,同时弹出 stackMin 的栈顶元素。
- 压入操作和弹出操作是对应的。
- 查询当前栈中的最小值:
- 由压入规则和弹出规则可知,stackMin 始终记录着 stackData 中的最小值。所以,最小值即为 stackMin 的栈顶元素。
【分析】
相同点:
- 方案一和方案二都是用 stackMin 保存着 stackData 每一步的最小值;
- 所有操作的时间复杂度都是 O(1),空间复杂度都是 O(n)。
不同点:
- 方案一中 stackMin 压入时稍省空间,但是弹出时稍费时间;
- 方案二中 stackMin 压入时稍费空间,但是弹出时稍省时间。
【实现】
- MyStack.java
public interface MyStack {
/**
* 入栈
*
* @param newNum 压入堆栈
* @throws Exception 栈为空
*/
void push(int newNum) throws Exception;
/**
* 出栈
*
* @return 弹出堆栈
* @throws Exception 栈为空
*/
int pop() throws Exception;
/**
* 获得栈中最小元素
*
* @return 栈中最小元素
* @throws Exception 栈为空
*/
int getMin() throws Exception;
}
- AbstractMyStack.java
import java.util.Stack;
public abstract class AbstractMyStack implements MyStack {
protected Stack<Integer> stackData;
protected Stack<Integer> stackMin;
public AbstractMyStack() {
stackData = new Stack<>();
stackMin = new Stack<>();
}
}
- MyStack1.java
public class MyStack1 extends AbstractMyStack {
@Override
public void push(int newNum) throws Exception {
if (this.stackMin.isEmpty() || newNum <= this.getMin()) { // 不能写成 newNum < this.getMin()
this.stackMin.push(newNum);
}
this.stackData.push(newNum);
}
@Override
public int pop() throws Exception {
if (this.stackData.isEmpty()) {
throw new RuntimeException("栈为空");
}
if (this.stackData.peek() == this.getMin()) {
this.stackMin.pop();
}
return this.stackData.pop();
}
@Override
public int getMin() throws Exception {
if (this.stackData.isEmpty()) {
throw new RuntimeException("栈为空");
}
return this.stackMin.peek();
}
}
- MyStack2.java
public class MyStack2 extends AbstractMyStack {
@Override
public void push(int newNum) throws Exception {
if (this.stackMin.isEmpty() || newNum <= this.getMin()) {
this.stackMin.push(newNum);
} else {
this.stackMin.push(this.stackMin.peek());
}
this.stackData.push(newNum);
}
@Override
public int pop() throws Exception {
if (this.stackData.isEmpty()) {
throw new RuntimeException("栈为空");
}
this.stackMin.pop();
return this.stackData.pop();
}
@Override
public int getMin() throws Exception {
if (this.stackData.isEmpty()) {
throw new RuntimeException("栈为空");
}
return this.stackMin.peek();
}
}
- MyStackTest.java
public class MyStackTest {
public static void main(String[] args) throws Exception {
MyStack stack1 = new MyStack1();
MyStack stack2 = new MyStack2();
test(stack1);
test(stack2);
}
private static void test(MyStack stack) throws Exception {
if (stack == null) {
throw new RuntimeException("参数不合法!");
}
stack.push(3);
stack.getMin();
stack.push(4);
stack.push(5);
stack.push(1);
stack.push(2);
stack.push(1);
stack.getMin();
stack.pop();
stack.pop();
stack.pop();
stack.pop();
stack.pop();
stack.getMin();
stack.pop();
}
}