1.图书整理II
读者来到图书馆排队借还书,图书管理员使用两个书车来完成整理借还书的任务。书车中的书从下往上叠加存放,图书管理员每次只能拿取书车顶部的书。排队的读者会有两种操作:
push(bookID)
:把借阅的书籍还到图书馆。pop()
:从图书馆中借出书籍。
为了保持图书的顺序,图书管理员每次取出供读者借阅的书籍是 最早 归还到图书馆的书籍。你需要返回 每次读者借出书的值 。
如果没有归还的书可以取出,返回 -1
。
示例 1:
输入:
["BookQueue", "push", "push", "pop"]
[[], [1], [2], []]
输出:[null,null,null,1]
解释:MyQueue myQueue = new MyQueue();
myQueue.push(1); // queue is: [1]
myQueue.push(2); // queue is: [1, 2] (leftmost is front of the queue)
myQueue.pop(); // return 1, queue is [2]
提示:
1 <= bookID <= 10000
- 最多会对
push
、pop
进行10000
次调用
用两个栈实现队列操作总结
题目通过两个栈的配合,实现队列的两大操作:队尾插入(appendTail)和队首删除(deleteHead)。以下是实现逻辑的详细总结。
核心思想
- 使用两个栈
A
和B
:- 栈 A:用于保存新插入的元素(队尾操作)。
- 栈 B:用于保存倒序的元素(队首操作)。
- 倒序逻辑:
- 当
B
为空时,将A
中所有元素出栈并入栈到B
,使B
中的顺序与队列的顺序一致。
- 当
- 操作分工:
appendTail(value)
:直接将元素压入栈A
。deleteHead()
:- 若栈
B
不为空,则弹出并返回B
的栈顶元素。 - 若栈
B
为空但栈A
不为空,将栈A
中所有元素转移到栈B
,然后从B
出栈。 - 若两个栈都为空,返回
-1
。
- 若栈
代码实现
java
import java.util.LinkedList;
class CQueue {
private LinkedList<Integer> A; // 栈 A
private LinkedList<Integer> B; // 栈 B
// 构造函数,初始化两个栈
public CQueue() {
A = new LinkedList<>();
B = new LinkedList<>();
}
// 队尾插入操作
public void appendTail(int value) {
A.addLast(value); // 将元素压入栈 A
}
// 队首删除操作
public int deleteHead() {
if (!B.isEmpty()) {
return B.removeLast(); // 栈 B 不为空时,弹出并返回栈顶元素
}
if (A.isEmpty()) {
return -1; // 两个栈都为空时,返回 -1
}
// 将栈 A 中的所有元素转移到栈 B
while (!A.isEmpty()) {
B.addLast(A.removeLast());
}
return B.removeLast(); // 返回栈 B 的栈顶元素
}
}
操作示例
以输入和输出为例:
java
CQueue myQueue = new CQueue();
myQueue.appendTail(1); // 栈 A: [1], 栈 B: []
myQueue.appendTail(2); // 栈 A: [1, 2], 栈 B: []
System.out.println(myQueue.deleteHead()); // 输出: 1, 栈 A: [], 栈 B: [2]
复杂度分析
- 时间复杂度:
appendTail
:仅对栈A
操作,时间复杂度为 O(1)。deleteHead
:- 栈
B
不为空时,直接出栈操作,时间复杂度为 O(1)。 - 栈
B
为空时,需要将栈A
的所有元素转移到栈B
,每个元素只转移一次,因此均摊复杂度为 O(1)。
- 栈
- 总体时间复杂度:O(1)(均摊)。
- 空间复杂度:两个栈最多存储 N 个元素,空间复杂度为 O(N)。
2.最小栈
请你设计一个 最小栈 。它提供 push
,pop
,top
操作,并能在常数时间内检索到最小元素的栈。
实现 MinStack
类:
MinStack()
初始化堆栈对象。void push(int val)
将元素val推入堆栈。void pop()
删除堆栈顶部的元素。int top()
获取堆栈顶部的元素。int getMin()
获取堆栈中的最小元素。
示例 1:
输入:
["MinStack","push","push","push","getMin","pop","top","getMin"]
[[],[-2],[2],[-3],[],[],[],[]]
输出:
[null,null,null,null,-3,null,2,-2]
解释:
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(2);
minStack.push(-3);
minStack.getMin(); --> 返回 -3.
minStack.pop();
minStack.top(); --> 返回 2.
minStack.getMin(); --> 返回 -2.
提示:
-231 <= val <= 231 - 1
pop
、top
和getMin
操作总是在 非空栈 上调用push
、pop
、top
和getMin
最多被调用3 * 104
次
用两个栈实现支持获取最小值的栈
题目难点
普通栈的基本操作(push()
、pop()
、top()
)时间复杂度为 O(1)。但在获取最小值时,直接遍历栈会使 getMin()
的时间复杂度变为 O(N)。
目标是实现一个栈,并保证:
- 所有操作的时间复杂度为 O(1) ,包括
getMin()
。
解题思路
利用两个栈来分别存储数据和辅助信息:
- 数据栈
A
:- 存储所有压入的数据元素。
- 保证常规的栈操作(
push
、pop
、top
)正常。
- 辅助栈
B
:- 始终维护一个非严格降序子序列,即栈顶为当前栈的最小值。
- 每次压入或弹出时,保持与数据栈的最小值对应关系。
辅助栈的作用:
- 压入元素时:
- 如果栈为空或当前元素小于等于栈顶元素,将元素同步压入辅助栈。
- 弹出元素时:
- 如果弹出的元素等于辅助栈的栈顶元素,辅助栈同步弹出。
方法设计
push(x)
:- 数据栈
A
添加元素x
。 - 若
B
为空或x ≤ B.peek()
,将x
压入辅助栈B
。
- 数据栈
pop()
:- 从数据栈
A
弹出一个元素,记为y
。 - 若
y == B.peek()
,从辅助栈B
同步弹出。
- 从数据栈
top()
:- 返回数据栈
A
的栈顶元素。
- 返回数据栈
getMin()
:- 返回辅助栈
B
的栈顶元素,即当前栈的最小值。
- 返回辅助栈
代码实现
java
import java.util.Stack;
class MinStack {
private Stack<Integer> A; // 数据栈
private Stack<Integer> B; // 辅助栈(存储最小值)
// 初始化栈
public MinStack() {
A = new Stack<>();
B = new Stack<>();
}
// 压入栈操作
public void push(int x) {
A.push(x); // 压入数据栈
// 如果辅助栈为空或者当前元素 <= 辅助栈顶,则同步压入
if (B.isEmpty() || x <= B.peek()) {
B.push(x);
}
}
// 弹出栈操作
public void pop() {
// 如果弹出的元素等于辅助栈栈顶元素,则辅助栈同步弹出
if (A.pop().equals(B.peek())) {
B.pop();
}
}
// 获取栈顶元素
public int top() {
return A.peek();
}
// 获取最小值
public int getMin() {
return B.peek(); // 辅助栈顶始终存储当前栈的最小值
}
}
操作示例
java
public class Main {
public static void main(String[] args) {
MinStack minStack = new MinStack();
minStack.push(3); // 数据栈: [3], 辅助栈: [3]
minStack.push(4); // 数据栈: [3, 4], 辅助栈: [3]
minStack.push(2); // 数据栈: [3, 4, 2], 辅助栈: [3, 2]
minStack.push(2); // 数据栈: [3, 4, 2, 2], 辅助栈: [3, 2, 2]
minStack.push(5); // 数据栈: [3, 4, 2, 2, 5], 辅助栈: [3, 2, 2]
System.out.println(minStack.getMin()); // 输出: 2
minStack.pop(); // 数据栈: [3, 4, 2, 2], 辅助栈: [3, 2, 2]
System.out.println(minStack.getMin()); // 输出: 2
minStack.pop(); // 数据栈: [3, 4, 2], 辅助栈: [3, 2]
System.out.println(minStack.getMin()); // 输出: 2
}
}
复杂度分析
- 时间复杂度:
push()
、pop()
、top()
和getMin()
操作均为 O(1),因为每次只需操作一个或两个栈的栈顶元素。
- 空间复杂度:
- 最差情况下,所有元素都被压入辅助栈,空间复杂度为 O(N)。