1. 栈的基础概念与Java实现
栈(Stack)是一种遵循后进先出(LIFO)原则的线性数据结构。想象一下餐厅里叠放的盘子——你总是从最上面取用,最后放上去的盘子会最先被拿走。在Java中,栈通常通过java.util.Stack类实现,但更推荐使用Deque接口的实现类(如ArrayDeque),因为Stack类由于设计问题已被标记为遗留类。
java复制// 使用Deque作为栈的标准实现方式
Deque<Integer> stack = new ArrayDeque<>();
stack.push(1); // 入栈
int top = stack.pop(); // 出栈
栈的核心操作时间复杂度均为O(1):
push(E item):压栈操作pop():弹栈操作peek():查看栈顶元素isEmpty():判断栈是否为空
注意:在实际工程中,应避免直接使用
Stack类。Java官方文档明确建议使用Deque接口及其实现类来替代Stack,因为Deque提供了更完整的双端队列操作,且性能更优。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 栈在算法题中的典型应用场景
2.1 括号匹配问题
这是栈最经典的入门级应用。给定一个只包含'(', ')', '{', '}', '[', ']'的字符串,判断括号是否有效匹配。栈的LIFO特性完美匹配括号的嵌套关系:
java复制public boolean isValid(String s) {
Deque<Character> stack = new ArrayDeque<>();
for (char c : s.toCharArray()) {
if (c == '(') stack.push(')');
else if (c == '{') stack.push('}');
else if (c == '[') stack.push(']');
else if (stack.isEmpty() || stack.pop() != c)
return false;
}
return stack.isEmpty();
}
2.2 表达式求值
栈在处理中缀表达式(如3 + 4 * 2 / (1 - 5))时表现出色。需要两个栈:一个存放操作数,一个存放运算符。算法核心是处理运算符优先级和括号:
- 遇到数字直接入操作数栈
- 遇到运算符时,比较与栈顶运算符的优先级
- 当前运算符优先级≤栈顶时,先计算栈顶运算
- 遇到左括号入栈,右括号则不断出栈计算直到左括号
2.3 单调栈应用
单调栈是指栈内元素保持单调递增或递减的顺序,常用于解决"下一个更大元素"类问题。例如LeetCode 496题:
java复制public int[] nextGreaterElement(int[] nums1, int[] nums2) {
Map<Integer, Integer> map = new HashMap<>();
Deque<Integer> stack = new ArrayDeque<>();
for (int num : nums2) {
while (!stack.isEmpty() && num > stack.peek()) {
map.put(stack.pop(), num);
}
stack.push(num);
}
int[] res = new int[nums1.length];
for (int i = 0; i < nums1.length; i++) {
res[i] = map.getOrDefault(nums1[i], -1);
}
return res;
}
3. 栈相关算法题精讲
3.1 最小栈问题(LeetCode 155)
设计一个支持push、pop、top操作,并能在常数时间内检索到最小元素的栈。关键在于使用辅助栈同步记录最小值:
java复制class MinStack {
private Deque<Integer> stack;
private Deque<Integer> minStack;
public MinStack() {
stack = new ArrayDeque<>();
minStack = new ArrayDeque<>();
minStack.push(Integer.MAX_VALUE);
}
public void push(int val) {
stack.push(val);
minStack.push(Math.min(minStack.peek(), val));
}
public void pop() {
stack.pop();
minStack.pop();
}
public int top() {
return stack.peek();
}
public int getMin() {
return minStack.peek();
}
}
3.2 柱状图中最大矩形(LeetCode 84)
这是单调栈的经典难题。我们需要找到每个柱子左右两边第一个比它矮的柱子,从而确定以该柱子为高的最大矩形面积:
java复制public int largestRectangleArea(int[] heights) {
Deque<Integer> stack = new ArrayDeque<>();
int maxArea = 0;
int n = heights.length;
for (int i = 0; i <= n; i++) {
int h = (i == n) ? 0 : heights[i];
while (!stack.isEmpty() && h < heights[stack.peek()]) {
int height = heights[stack.pop()];
int width = stack.isEmpty() ? i : i - stack.peek() - 1;
maxArea = Math.max(maxArea, height * width);
}
stack.push(i);
}
return maxArea;
}
3.3 逆波兰表达式求值(LeetCode 150)
逆波兰表达式(后缀表达式)的计算是栈的典型应用,运算符总是放在操作数之后:
java复制public int evalRPN(String[] tokens) {
Deque<Integer> stack = new ArrayDeque<>();
for (String token : tokens) {
if (!"+-*/".contains(token)) {
stack.push(Integer.parseInt(token));
continue;
}
int num2 = stack.pop();
int num1 = stack.pop();
int res = 0;
switch (token) {
case "+": res = num1 + num2; break;
case "-": res = num1 - num2; break;
case "*": res = num1 * num2; break;
case "/": res = num1 / num2; break;
}
stack.push(res);
}
return stack.pop();
}
4. 栈的进阶应用与面试技巧
4.1 递归与栈的关系
所有递归算法都可以用栈+循环来实现。以二叉树的中序遍历为例:
递归版本:
java复制void inorder(TreeNode root) {
if (root == null) return;
inorder(root.left);
System.out.println(root.val);
inorder(root.right);
}
栈迭代版本:
java复制void inorder(TreeNode root) {
Deque<TreeNode> stack = new ArrayDeque<>();
TreeNode curr = root;
while (curr != null || !stack.isEmpty()) {
while (curr != null) {
stack.push(curr);
curr = curr.left;
}
curr = stack.pop();
System.out.println(curr.val);
curr = curr.right;
}
}
4.2 栈在DFS中的应用
深度优先搜索(DFS)天然适合用栈实现。以下是用栈实现的非递归DFS模板:
java复制void dfs(Node root) {
if (root == null) return;
Deque<Node> stack = new ArrayDeque<>();
Set<Node> visited = new HashSet<>();
stack.push(root);
visited.add(root);
while (!stack.isEmpty()) {
Node node = stack.pop();
// 处理当前节点
for (Node neighbor : node.neighbors) {
if (!visited.contains(neighbor)) {
visited.add(neighbor);
stack.push(neighbor);
}
}
}
}
4.3 面试常见问题与解答技巧
- 边界条件处理:空栈的pop/peek操作、栈溢出(虽然Java中不常见)
- 时间空间复杂度分析:栈操作通常是O(1),但整体算法复杂度取决于问题规模
- 变种问题识别:如"用队列实现栈"考察的是对数据结构本质的理解
- 代码简洁性:合理利用Java API(如Deque的方法)减少代码量
- 测试用例设计:应包括正常情况、边界情况(空输入、极端值)和非法输入
面试实战技巧:当遇到涉及顺序反转、嵌套结构(如括号、HTML标签)或需要记忆先前状态的问题时,应优先考虑栈的解决方案。解释思路时,可以画图辅助说明栈的变化过程。
