1. 最小栈(LeetCode 155)
问题分析
设计一个支持 push、pop、top 操作,并能在常数时间内检索到最小元素的栈。
解题思路:双栈法
- 使用两个栈:一个正常存储数据,另一个存储当前最小值
- 最小栈的栈顶始终是数据栈中的最小值
- 入栈时同步更新最小栈,出栈时同步移除
代码实现与优化
java
class MinStack {
private Stack<Integer> dataStack;
private Stack<Integer> minStack;
public MinStack() {
dataStack = new Stack<>();
minStack = new Stack<>();
}
public void push(int val) {
dataStack.push(val);
// 最小栈为空或当前值 <= 最小栈顶时入栈
if (minStack.isEmpty() || val <= minStack.peek()) {
minStack.push(val);
}
}
public void pop() {
if (dataStack.isEmpty()) return;
int popped = dataStack.pop();
// 如果弹出的是最小值,同步移除最小栈顶
if (popped == minStack.peek()) {
minStack.pop();
}
}
public int top() {
if (dataStack.isEmpty()) throw new RuntimeException("Stack is empty");
return dataStack.peek();
}
public int getMin() {
if (minStack.isEmpty()) throw new RuntimeException("Stack is empty");
return minStack.peek();
}
}
复杂度分析
- 时间复杂度:所有操作 O(1)
- 空间复杂度:O(n)
2. 有效的括号(LeetCode 20)
问题分析
给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。
解题思路:栈匹配法
- 遇到左括号入栈
- 遇到右括号检查栈顶是否匹配
- 最终栈为空则有效
代码实现
java
class Solution {
public boolean isValid(String s) {
Stack<Character> stack = new Stack<>();
for (char c : s.toCharArray()) {
// 左括号入栈
if (c == '(' || c == '[' || c == '{') {
stack.push(c);
}
// 右括号检查匹配
else {
if (stack.isEmpty()) return false;
char top = stack.pop();
if ((c == ')' && top != '(') ||
(c == ']' && top != '[') ||
(c == '}' && top != '{')) {
return false;
}
}
}
return stack.isEmpty();
}
}
使用HashMap的优化版本
java
class Solution {
public boolean isValid(String s) {
Stack<Character> stack = new Stack<>();
Map<Character, Character> map = new HashMap<>();
map.put(')', '(');
map.put(']', '[');
map.put('}', '{');
for (char c : s.toCharArray()) {
if (!map.containsKey(c)) {
// 左括号
stack.push(c);
} else {
// 右括号
if (stack.isEmpty() || stack.pop() != map.get(c)) {
return false;
}
}
}
return stack.isEmpty();
}
}
3. 栈的压入、弹出序列
问题分析
判断第二个序列是否可能为第一个序列的弹出顺序。
解题思路:模拟出入栈
- 遍历压入序列,模拟入栈操作
- 每次入栈后检查栈顶是否等于弹出序列当前元素
- 相等则出栈并移动弹出序列指针
代码实现
java
import java.util.Stack;
public class Solution {
public boolean IsPopOrder(int[] pushA, int[] popA) {
if (pushA == null || popA == null || pushA.length != popA.length) {
return false;
}
Stack<Integer> stack = new Stack<>();
int popIndex = 0;
for (int i = 0; i < pushA.length; i++) {
stack.push(pushA[i]);
// 检查是否可以弹出
while (!stack.isEmpty() && stack.peek() == popA[popIndex]) {
stack.pop();
popIndex++;
}
}
return stack.isEmpty();
}
}
4. 逆波兰表达式求值(LeetCode 150)
问题分析
根据逆波兰表示法(后缀表达式)求表达式的值。
中缀转后缀表达式方法
以 (1+2)*3+4 为例:
- 加括号明确优先级:
(((1+2)*3)+4) - 将运算符移到对应括号后面:
(((12+)3*)4+) - 去除括号:
12+3*4+
解题思路:栈计算法
- 遇到数字入栈
- 遇到运算符弹出两个数字运算后结果入栈
- 注意除法和减法的顺序
代码实现
java
class Solution {
public int evalRPN(String[] tokens) {
Stack<Integer> stack = new Stack<>();
for (String token : tokens) {
if (isOperator(token)) {
int b = stack.pop(); // 注意顺序:先弹出的是右操作数
int a = stack.pop(); // 后弹出的是左操作数
int result = calculate(a, b, token);
stack.push(result);
} else {
stack.push(Integer.parseInt(token));
}
}
return stack.pop();
}
private boolean isOperator(String token) {
return token.equals("+") || token.equals("-") ||
token.equals("*") || token.equals("/");
}
private int calculate(int a, int b, String operator) {
switch (operator) {
case "+": return a + b;
case "-": return a - b;
case "*": return a * b;
case "/": return a / b; // 题目保证除法为整数除法
default: throw new IllegalArgumentException("Invalid operator");
}
}
}
5. 用队列实现栈(LeetCode 225)
问题分析
使用队列实现栈的 push、pop、top、empty 操作。
解题思路:双队列法
- 入栈时直接加入非空队列
- 出栈时将非空队列的前 n-1 个元素转移到空队列,弹出最后一个元素
- 始终保持一个队列为空
代码实现
java
class MyStack {
private Queue<Integer> queue1;
private Queue<Integer> queue2;
public MyStack() {
queue1 = new LinkedList<>();
queue2 = new LinkedList<>();
}
public void push(int x) {
// 总是加入非空队列
if (!queue1.isEmpty()) {
queue1.offer(x);
} else {
queue2.offer(x);
}
}
public int pop() {
if (empty()) throw new RuntimeException("Stack is empty");
if (!queue1.isEmpty()) {
// 将 queue1 的前 n-1 个元素移到 queue2
while (queue1.size() > 1) {
queue2.offer(queue1.poll());
}
return queue1.poll();
} else {
// 将 queue2 的前 n-1 个元素移到 queue1
while (queue2.size() > 1) {
queue1.offer(queue2.poll());
}
return queue2.poll();
}
}
public int top() {
if (empty()) throw new RuntimeException("Stack is empty");
int topValue;
if (!queue1.isEmpty()) {
// 将 queue1 的所有元素移到 queue2,记录最后一个
while (!queue1.isEmpty()) {
topValue = queue1.peek();
queue2.offer(queue1.poll());
}
} else {
// 将 queue2 的所有元素移到 queue1,记录最后一个
while (!queue2.isEmpty()) {
topValue = queue2.peek();
queue1.offer(queue2.poll());
}
}
return topValue;
}
public boolean empty() {
return queue1.isEmpty() && queue2.isEmpty();
}
}
优化版本(单队列法)
java
class MyStack {
private Queue<Integer> queue;
public MyStack() {
queue = new LinkedList<>();
}
public void push(int x) {
queue.offer(x);
// 将新元素前面的所有元素重新入队,使其成为队首
for (int i = 0; i < queue.size() - 1; i++) {
queue.offer(queue.poll());
}
}
public int pop() {
return queue.poll();
}
public int top() {
return queue.peek();
}
public boolean empty() {
return queue.isEmpty();
}
}
6. 用栈实现队列(LeetCode 232)
问题分析
使用栈实现队列的 push、pop、peek、empty 操作。
解题思路:双栈法
- 输入栈:负责接收 push 操作
- 输出栈:负责 pop 和 peek 操作
- 当输出栈为空时,将输入栈所有元素转移到输出栈
代码实现
java
class MyQueue {
private Stack<Integer> inStack;
private Stack<Integer> outStack;
public MyQueue() {
inStack = new Stack<>();
outStack = new Stack<>();
}
public void push(int x) {
inStack.push(x);
}
public int pop() {
if (empty()) throw new RuntimeException("Queue is empty");
// 如果输出栈为空,转移所有元素
if (outStack.isEmpty()) {
while (!inStack.isEmpty()) {
outStack.push(inStack.pop());
}
}
return outStack.pop();
}
public int peek() {
if (empty()) throw new RuntimeException("Queue is empty");
// 如果输出栈为空,转移所有元素
if (outStack.isEmpty()) {
while (!inStack.isEmpty()) {
outStack.push(inStack.pop());
}
}
return outStack.peek();
}
public boolean empty() {
return inStack.isEmpty() && outStack.isEmpty();
}
}
7. 设计循环队列(LeetCode 622)
问题分析
设计一个循环队列,支持 enQueue、deQueue、Front、Rear、isEmpty、isFull 操作。
循环队列设计要点
- 使用数组实现,维护 front 和 rear 指针
- 队列空:front == rear
- 队列满:(rear + 1) % capacity == front
- 浪费一个空间来区分空和满的状态
代码实现
java
class MyCircularQueue {
private int[] data;
private int front; // 队首指针
private int rear; // 队尾指针(指向下一个插入位置)
private int capacity;
public MyCircularQueue(int k) {
capacity = k + 1; // 多分配一个空间用于判断满
data = new int[capacity];
front = 0;
rear = 0;
}
public boolean enQueue(int value) {
if (isFull()) return false;
data[rear] = value;
rear = (rear + 1) % capacity;
return true;
}
public boolean deQueue() {
if (isEmpty()) return false;
front = (front + 1) % capacity;
return true;
}
public int Front() {
if (isEmpty()) return -1;
return data[front];
}
public int Rear() {
if (isEmpty()) return -1;
// rear 指向下一个插入位置,队尾元素在 rear-1 位置
return data[(rear - 1 + capacity) % capacity];
}
public boolean isEmpty() {
return front == rear;
}
public boolean isFull() {
return (rear + 1) % capacity == front;
}
}
不使用浪费空间的版本
java
class MyCircularQueue {
private int[] data;
private int front;
private int rear;
private int size; // 当前元素个数
public MyCircularQueue(int k) {
data = new int[k];
front = 0;
rear = 0;
size = 0;
}
public boolean enQueue(int value) {
if (isFull()) return false;
data[rear] = value;
rear = (rear + 1) % data.length;
size++;
return true;
}
public boolean deQueue() {
if (isEmpty()) return false;
front = (front + 1) % data.length;
size--;
return true;
}
public int Front() {
if (isEmpty()) return -1;
return data[front];
}
public int Rear() {
if (isEmpty()) return -1;
return data[(rear - 1 + data.length) % data.length];
}
public boolean isEmpty() {
return size == 0;
}
public boolean isFull() {
return size == data.length;
}
}
算法技巧总结
栈的常见应用场景
- 括号匹配:使用栈检查嵌套结构
- 表达式求值:中缀转后缀,后缀表达式求值
- 函数调用:模拟递归调用栈
- 浏览器历史:前进后退功能
队列的常见应用场景
- BFS遍历:图的广度优先搜索
- 任务调度:CPU任务调度,消息队列
- 缓存实现:LRU缓存淘汰算法
设计模式总结
- 最小栈:双栈同步维护
- 栈实现队列:输入栈 + 输出栈
- 队列实现栈:双队列或单队列重排
- 循环队列:数组 + 指针模运算
复杂度分析要点
- 栈和队列的基本操作通常为 O(1)
- 注意边界条件:空栈/空队列操作
- 循环队列注意指针回绕处理
这些题目涵盖了栈和队列的核心应用场景,掌握后能够解决大多数相关问题。建议重点理解双栈法和双队列法的设计思想,这在很多系统设计中都有应用。