1. 栈数据结构基础认知
栈(Stack)作为计算机科学中最基础的数据结构之一,其核心特性可以概括为"后进先出"(LIFO)原则。这种特性使得栈在解决特定类型问题时展现出极高的效率,特别是在需要"撤销"或"回退"操作的场景中。
栈的基本操作主要包含三个核心方法:
- push(入栈):将元素添加到栈顶
- pop(出栈):移除并返回栈顶元素
- peek(查看栈顶):获取但不移除栈顶元素
在Java中,Stack类直接提供了这些方法的实现:
java复制Stack<Integer> stack = new Stack<>();
stack.push(1); // 栈:[1]
stack.push(2); // 栈:[1, 2]
int top = stack.pop(); // 返回2,栈:[1]
注意:Java官方文档建议使用Deque接口替代Stack类,因为Stack继承自Vector存在设计缺陷。实际开发中更推荐使用ArrayDeque:
java复制Deque<Integer> stack = new ArrayDeque<>();
2. LeetCode Hot 100栈专题精析
2.1 有效括号问题(No.20)
这是栈结构的经典应用场景。题目要求判断字符串中的括号是否有效闭合,不同类型的括号必须正确嵌套。
解决方案的核心思路:
- 遇到左括号就压栈
- 遇到右括号就检查栈顶是否匹配
- 最终栈应为空
python复制def isValid(s: str) -> bool:
stack = []
mapping = {')': '(', '}': '{', ']': '['}
for char in s:
if char in mapping:
top = stack.pop() if stack else '#'
if mapping[char] != top:
return False
else:
stack.append(char)
return not stack
时间复杂度:O(n),空间复杂度:O(n)
2.2 最小栈设计(No.155)
这道题要求在常数时间内检索到最小元素,常规思路需要额外空间来存储最小值信息。
优化方案是使用辅助栈同步存储最小值:
java复制class MinStack {
private Deque<Integer> dataStack;
private Deque<Integer> minStack;
public MinStack() {
dataStack = new ArrayDeque<>();
minStack = new ArrayDeque<>();
minStack.push(Integer.MAX_VALUE);
}
public void push(int val) {
dataStack.push(val);
minStack.push(Math.min(minStack.peek(), val));
}
public void pop() {
dataStack.pop();
minStack.pop();
}
public int top() {
return dataStack.peek();
}
public int getMin() {
return minStack.peek();
}
}
关键点:每次push时,minStack同步push当前最小值;pop时同步pop,保证两个栈深度一致。
2.3 柱状图最大矩形(No.84)
这是栈应用的进阶难题,需要找到柱状图中最大的矩形面积。暴力解法O(n²)会超时,需要使用单调栈优化。
单调栈解法步骤:
- 维护一个单调递增栈
- 当遇到较小元素时,计算之前较高柱子的面积
- 在数组前后添加哨兵节点简化边界处理
python复制def largestRectangleArea(heights):
heights = [0] + heights + [0]
stack = []
max_area = 0
for i in range(len(heights)):
while stack and heights[i] < heights[stack[-1]]:
h = heights[stack.pop()]
w = i - stack[-1] - 1
max_area = max(max_area, h * w)
stack.append(i)
return max_area
时间复杂度优化到O(n),是典型的空间换时间案例。
3. 单调栈的深入应用
3.1 每日温度问题(No.739)
题目要求对于每一天,找到需要等待多少天才能遇到更高温度。这是单调栈的典型应用场景。
解决方案使用递减栈:
java复制public int[] dailyTemperatures(int[] temperatures) {
int[] result = new int[temperatures.length];
Deque<Integer> stack = new ArrayDeque<>();
for (int i = 0; i < temperatures.length; i++) {
while (!stack.isEmpty() && temperatures[i] > temperatures[stack.peek()]) {
int idx = stack.pop();
result[idx] = i - idx;
}
stack.push(i);
}
return result;
}
3.2 接雨水问题(No.42)
这是单调栈的另一个经典应用,计算柱子之间的积水面积。可以采用双指针或单调栈两种解法。
单调栈解法:
python复制def trap(height):
stack = []
water = 0
for i in range(len(height)):
while stack and height[i] > height[stack[-1]]:
bottom = height[stack.pop()]
if not stack:
break
distance = i - stack[-1] - 1
bounded_height = min(height[i], height[stack[-1]]) - bottom
water += distance * bounded_height
stack.append(i)
return water
4. 栈的扩展应用场景
4.1 字符串解码(No.394)
这类嵌套结构解析问题天然适合用栈解决。需要处理数字、字母和括号三种元素。
解决方案:
java复制public String decodeString(String s) {
Deque<Integer> numStack = new ArrayDeque<>();
Deque<StringBuilder> strStack = new ArrayDeque<>();
StringBuilder current = new StringBuilder();
int num = 0;
for (char c : s.toCharArray()) {
if (Character.isDigit(c)) {
num = num * 10 + (c - '0');
} else if (c == '[') {
numStack.push(num);
strStack.push(current);
current = new StringBuilder();
num = 0;
} else if (c == ']') {
StringBuilder temp = current;
current = strStack.pop();
int repeat = numStack.pop();
current.append(temp.toString().repeat(repeat));
} else {
current.append(c);
}
}
return current.toString();
}
4.2 二叉树的中序遍历(No.94)
虽然递归解法简单,但栈实现的迭代解法更能体现对栈的理解:
python复制def inorderTraversal(root):
stack = []
result = []
curr = root
while curr or stack:
while curr:
stack.append(curr)
curr = curr.left
curr = stack.pop()
result.append(curr.val)
curr = curr.right
return result
5. 栈与其他数据结构的组合应用
5.1 用队列实现栈(No.225)
虽然题目要求用队列实现栈,但实际工程中更常见的是用双栈实现特定功能。
解法示例:
java复制class MyStack {
private Queue<Integer> queue;
public MyStack() {
queue = new LinkedList<>();
}
public void push(int x) {
queue.offer(x);
// 将新元素前面的所有元素重新入队
for (int i = 1; i < queue.size(); i++) {
queue.offer(queue.poll());
}
}
public int pop() {
return queue.poll();
}
public int top() {
return queue.peek();
}
public boolean empty() {
return queue.isEmpty();
}
}
5.2 用栈实现队列(No.232)
与上题相反,需要两个栈来模拟队列的FIFO特性:
python复制class MyQueue:
def __init__(self):
self.input = []
self.output = []
def push(self, x):
self.input.append(x)
def pop(self):
self.peek()
return self.output.pop()
def peek(self):
if not self.output:
while self.input:
self.output.append(self.input.pop())
return self.output[-1]
def empty(self):
return not self.input and not self.output
6. 栈在系统设计中的应用
6.1 函数调用栈
程序执行时的函数调用正是栈的典型应用:
- 每次函数调用时,将返回地址和局部变量压栈
- 函数返回时,从栈顶弹出返回地址
- 递归过深会导致栈溢出(StackOverflowError)
6.2 浏览器的前进后退
浏览器使用两个栈实现页面导航:
- 访问新页面时,压入前进栈
- 点击后退时,从前进栈弹出并压入后退栈
- 点击前进时,从后退栈弹出并压入前进栈
6.3 撤销操作实现
编辑器的撤销(Undo)功能通常使用操作栈:
- 每次编辑操作被记录到栈中
- 撤销时弹出最近的操作并执行反向操作
- 重做(Redo)通常使用另一个栈存储撤销的操作
7. 栈相关算法题解题模板
通过分析Hot100中的栈相关题目,可以总结出以下解题模板:
7.1 括号匹配类问题模板
- 初始化栈和映射表
- 遍历字符串:
- 开括号:压栈
- 闭括号:检查栈顶是否匹配
- 最后检查栈是否为空
7.2 单调栈解题模板
- 初始化空栈
- 遍历元素:
- while栈不空且当前元素破坏单调性:弹出栈顶并计算
- 压入当前元素
- 处理剩余栈中元素
7.3 嵌套结构解析模板
- 使用多个栈存储不同类型数据
- 遇到开始符号压栈
- 遇到结束符号弹出计算
- 注意数字可能有多位
8. 栈的优化技巧与常见错误
8.1 性能优化技巧
- 预设栈容量避免扩容开销
- 使用数组实现栈比链表更节省内存
- 对于固定大小需求,使用原生数组而非集合类
8.2 边界条件处理
- 栈空时的pop/peek操作
- 处理输入为null或空的情况
- 数值溢出问题(特别是min stack)
8.3 常见错误示例
java复制// 错误1:未检查栈空直接pop
int val = stack.pop(); // 可能抛出EmptyStackException
// 正确做法
if (!stack.isEmpty()) {
val = stack.pop();
}
// 错误2:数字栈和操作符栈不同步
numStack.push(1);
numStack.push(2);
opStack.push('+');
// 如果此时直接计算,顺序可能出错
// 正确做法:确保每次操作都有足够的操作数
9. 栈的替代实现方案
9.1 基于数组的实现
java复制class ArrayStack {
private int[] array;
private int top;
public ArrayStack(int capacity) {
array = new int[capacity];
top = -1;
}
public void push(int val) {
if (top == array.length - 1) {
throw new StackOverflowError();
}
array[++top] = val;
}
public int pop() {
if (isEmpty()) {
throw new EmptyStackException();
}
return array[top--];
}
}
9.2 基于链表的实现
python复制class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
class LinkedStack:
def __init__(self):
self.head = None
def push(self, val):
new_node = ListNode(val)
new_node.next = self.head
self.head = new_node
def pop(self):
if not self.head:
raise Exception("Empty stack")
val = self.head.val
self.head = self.head.next
return val
10. 栈在算法竞赛中的特殊应用
10.1 后缀表达式计算
栈是计算后缀表达式(逆波兰表示法)的理想数据结构:
- 遇到数字:压栈
- 遇到运算符:弹出两个数字计算后结果压栈
- 最后栈中剩余数字即为结果
10.2 单调栈解决区间极值
单调栈可以高效解决滑动窗口最大值、直方图最大矩形等区间极值问题,时间复杂度通常为O(n)。
10.3 离线RMQ问题
使用栈可以实现O(n)预处理、O(1)查询的Range Minimum Query算法,比线段树和ST表更节省空间。