《常见高频算法题 Java 解法实战精讲(2):堆栈与递归》

🔍常见高频算法题 Java 解法实战精讲(2):堆栈与递归

🧠写在前面:堆栈与递归题型为何频繁出现在面试中?

堆栈(Stack)与递归(Recursion)类题目属于经典的算法考察方向,原因如下:

  • ✅ 数据结构基础扎实度测试:考查是否真正理解"先进后出"的栈结构。

  • ✅ 代码逻辑清晰与抽象能力:递归常被用于处理树、图等结构,能快速验证抽象思维。

  • ✅ Java 线程控制/执行顺序的理解:堆栈和线程调度关系紧密,是并发考点基础。

  • ✅ 模板化解题能力评估:是否掌握栈操作与 DFS/BFS 的通用套路,体现工程实践力。

文章目录

  • [🔍常见高频算法题 Java 解法实战精讲(2):堆栈与递归](#🔍常见高频算法题 Java 解法实战精讲(2):堆栈与递归)
  • 一、堆栈与递归的核心地位
    • [💡 堆栈与递归的关系](#💡 堆栈与递归的关系)
    • [⚠️ 面试高频原因](#⚠️ 面试高频原因)
  • 二、堆栈类经典题
    • [💡 2.1 有效的括号(Valid Parentheses)](#💡 2.1 有效的括号(Valid Parentheses))
    • [💡 2.2 逆波兰表达式求值](#💡 2.2 逆波兰表达式求值)
  • 三、DFS与BFS模板实战
    • [💡 3.1 二叉树的最大深度(DFS模板)](#💡 3.1 二叉树的最大深度(DFS模板))
    • [💡 3.2 图的最短路径(BFS模板)](#💡 3.2 图的最短路径(BFS模板))
  • 四、多线程算法题
    • [💡 4.1 交替打印FooBar](#💡 4.1 交替打印FooBar)
    • [💡 4.2 按序打印ABC](#💡 4.2 按序打印ABC)
  • 五、高频面试套路总结
    • [💡 堆栈类题目套路](#💡 堆栈类题目套路)
    • [🔄 搜索算法模板](#🔄 搜索算法模板)
    • [⚡️ 多线程解题框架](#⚡️ 多线程解题框架)
    • [🛡️ 面试避坑指南](#🛡️ 面试避坑指南)

一、堆栈与递归的核心地位

💡 堆栈与递归的关系

递归 函数调用栈 栈帧 局部变量 返回地址 参数传递

⚠️ 面试高频原因

原因 考察点 出现频率
基础能力 函数调用机制 90%
思维转换 递归转迭代 75%
系统设计 栈溢出防护 60%
复杂问题 树/图遍历 85%

二、堆栈类经典题

💡 2.1 有效的括号(Valid Parentheses)

​​题目描述​​:

给定只包含'(', ')', '{', '}', '[', ']'的字符串,判断括号是否有效

​​Java解法​​:

java 复制代码
public boolean isValid(String s) {
    Deque<Character> stack = new ArrayDeque<>();
    Map<Character, Character> map = Map.of(')', '(', '}', '{', ']', '[');
    
    for (char c : s.toCharArray()) {
        if (map.containsValue(c)) {
            stack.push(c);
        } else if (stack.isEmpty() || stack.pop() != map.get(c)) {
            return false;
        }
    }
    return stack.isEmpty();
}

复杂度分析​​:

  • 时间复杂度:O(n) 单次遍历
  • 空间复杂度:O(n) 栈空间
    ​​常见陷阱​​:
java 复制代码
// 错误:未处理空栈情况
if (stack.pop() != map.get(c)) // 当栈空时抛出异常

// 正确:先检查栈空
if (stack.isEmpty() || stack.pop() != map.get(c))

💡 2.2 逆波兰表达式求值

​​题目描述​​:

根据逆波兰表示法(后缀表达式)求值,如["2","1","+","3","*"]→ (2+1)*3=9

​​Java解法​​:

java 复制代码
public int evalRPN(String[] tokens) {
    Deque<Integer> stack = new ArrayDeque<>();
    for (String token : tokens) {
        switch (token) {
            case "+":
                stack.push(stack.pop() + stack.pop());
                break;
            case "-":
                int b = stack.pop(), a = stack.pop();
                stack.push(a - b);
                break;
            case "*":
                stack.push(stack.pop() * stack.pop());
                break;
            case "/":
                int divisor = stack.pop(), dividend = stack.pop();
                stack.push(dividend / divisor);
                break;
            default:
                stack.push(Integer.parseInt(token));
        }
    }
    return stack.pop();
}

​​操作流程​​:
2 入栈 1 入栈 + 弹出1和2 计算2+1=3入栈 3 入栈 * 弹出3和3 计算3 * 3=9入栈

复杂度分析​​:

  • 时间复杂度:O(n)
  • 空间复杂度:O(n)

三、DFS与BFS模板实战

💡 3.1 二叉树的最大深度(DFS模板)

​​题目描述​​:

求二叉树的最大深度

​​递归解法​​:

java 复制代码
public int maxDepth(TreeNode root) {
    if (root == null) return 0;
    int left = maxDepth(root.left);
    int right = maxDepth(root.right);
    return Math.max(left, right) + 1;
}

​​迭代解法​​:

java 复制代码
public int maxDepth(TreeNode root) {
    if (root == null) return 0;
    Deque<TreeNode> stack = new ArrayDeque<>();
    Deque<Integer> depths = new ArrayDeque<>();
    stack.push(root);
    depths.push(1);
    int max = 0;
    
    while (!stack.isEmpty()) {
        TreeNode node = stack.pop();
        int depth = depths.pop();
        max = Math.max(max, depth);
        
        if (node.right != null) {
            stack.push(node.right);
            depths.push(depth + 1);
        }
        if (node.left != null) {
            stack.push(node.left);
            depths.push(depth + 1);
        }
    }
    return max;
}

​​DFS模板总结​​:

java 复制代码
// 递归模板
void dfs(Node node) {
    if (终止条件) return;
    for (Node child : node.children) {
        dfs(child);
    }
}

// 迭代模板
void dfs(Node root) {
    Stack<Node> stack = new Stack<>();
    stack.push(root);
    while (!stack.isEmpty()) {
        Node node = stack.pop();
        for (Node child : node.children) {
            stack.push(child);
        }
    }
}

💡 3.2 图的最短路径(BFS模板)

​​题目描述​​:

在无权图中求从起点到终点的最短路径

​​Java解法​​:

java 复制代码
public int shortestPath(int[][] graph, int start, int end) {
    Queue<Integer> queue = new LinkedList<>();
    boolean[] visited = new boolean[graph.length];
    int[] distance = new int[graph.length];
    
    queue.offer(start);
    visited[start] = true;
    
    while (!queue.isEmpty()) {
        int node = queue.poll();
        if (node == end) return distance[node];
        
        for (int neighbor : graph[node]) {
            if (!visited[neighbor]) {
                visited[neighbor] = true;
                distance[neighbor] = distance[node] + 1;
                queue.offer(neighbor);
            }
        }
    }
    return -1; // 不可达
}

​​BFS模板总结​​:

java 复制代码
void bfs(Node start) {
    Queue<Node> queue = new LinkedList<>();
    Set<Node> visited = new HashSet<>();
    queue.offer(start);
    visited.add(start);
    
    while (!queue.isEmpty()) {
        Node node = queue.poll();
        for (Node neighbor : node.neighbors) {
            if (!visited.contains(neighbor)) {
                visited.add(neighbor);
                queue.offer(neighbor);
            }
        }
    }
}

​​复杂度对比​​:

算法 时间复杂度 空间复杂度 适用场景
DFS O(V+E) O(h) 深度优先/路径存在
BFS O(V+E) O(w) 最短路径/层级遍历

四、多线程算法题

💡 4.1 交替打印FooBar

​​题目描述​​:

两个线程交替打印"foo"和"bar"各n次

​​Semaphore解法​​:

java 复制代码
class FooBar {
    private int n;
    private Semaphore fooSem = new Semaphore(1);
    private Semaphore barSem = new Semaphore(0);
    
    public FooBar(int n) { this.n = n; }
    
    public void foo(Runnable printFoo) throws InterruptedException {
        for (int i = 0; i < n; i++) {
            fooSem.acquire();
            printFoo.run();
            barSem.release();
        }
    }
    
    public void bar(Runnable printBar) throws InterruptedException {
        for (int i = 0; i < n; i++) {
            barSem.acquire();
            printBar.run();
            fooSem.release();
        }
    }
}

💡 4.2 按序打印ABC

​​题目描述​​:

三个线程按顺序循环打印"A""B""C"

​​ReentrantLock解法​​:

java 复制代码
class ABCPrinter {
    private ReentrantLock lock = new ReentrantLock();
    private Condition conditionA = lock.newCondition();
    private Condition conditionB = lock.newCondition();
    private Condition conditionC = lock.newCondition();
    private int state = 0; // 0:A, 1:B, 2:C
    
    public void printA() throws InterruptedException {
        lock.lock();
        try {
            while (state != 0) conditionA.await();
            System.out.print("A");
            state = 1;
            conditionB.signal();
        } finally {
            lock.unlock();
        }
    }
    
    public void printB() throws InterruptedException {
        lock.lock();
        try {
            while (state != 1) conditionB.await();
            System.out.print("B");
            state = 2;
            conditionC.signal();
        } finally {
            lock.unlock();
        }
    }
    
    public void printC() throws InterruptedException {
        lock.lock();
        try {
            while (state != 2) conditionC.await();
            System.out.print("C");
            state = 0;
            conditionA.signal();
        } finally {
            lock.unlock();
        }
    }
}

​​多线程解题技巧​​:
多线程同步 信号量 锁+条件变量 volatile+自旋 阻塞队列 简单同步 精细控制 低延迟 解耦生产消费

五、高频面试套路总结

💡 堆栈类题目套路

  1. 括号匹配

    • 使用栈存储左括号
    • 遇到右括号时弹出匹配
    • 最后检查栈是否为空
  2. 表达式求值

    • 中缀转后缀(调度场算法)
    • 后缀表达式求值(操作数栈)
    • 处理运算符优先级
  3. 单调栈

    • 解决"下一个更大元素"问题
    • 保持栈内元素单调性
    • 时间复杂度O(n)

🔄 搜索算法模板

java 复制代码
// DFS递归模板
void dfs(Node node, State state) {
    if (终止条件) {
        更新结果;
        return;
    }
    for (选择 : 当前选择列表) {
        做选择;
        dfs(下一节点, 新状态);
        撤销选择;
    }
}

// BFS模板
void bfs(Node start) {
    Queue<Node> queue = new LinkedList<>();
    Set<Node> visited = new HashSet<>();
    queue.offer(start);
    visited.add(start);
    
    while (!queue.isEmpty()) {
        Node node = queue.poll();
        for (Node neighbor : node.neighbors) {
            if (!visited.contains(neighbor)) {
                visited.add(neighbor);
                queue.offer(neighbor);
            }
        }
    }
}

⚡️ 多线程解题框架

java 复制代码
| 问题类型 | 推荐方案 | 关键点 |
|---------|---------|-------|
| **顺序控制** | 信号量 | acquire/release顺序 |
| **交替执行** | 条件变量 | await/signal精准通知 |
| **并行计算** | Future/CompletableFuture | 异步结果聚合 |
| **资源池** | Semaphore | 控制并发数 |

🛡️ 面试避坑指南

  1. 递归陷阱

    • 栈溢出:限制递归深度
    • 重复计算:使用记忆化
  2. DFS/BFS选择

    • DFS:路径存在性/所有解
    • BFS:最短路径/最少步数
  3. 多线程安全

    • 避免死锁:固定锁顺序
    • 防止饥饿:公平锁/超时
    • 可见性:volatile/原子类

​​模板是基础​​ :掌握DFS/BFS标准写法

​​递归转迭代​​ :避免栈溢出风险

​​线程安全第一​​ :多线程优先考虑正确性

记住:​​算法面试不是考记忆,而是考思维