【Hot100】8.二叉树

这一类的核心思想是:递归(DFS)与层次遍历(BFS)

  • 递归 (DFS):二叉树的天然定义就是递归的(根 + 左子树 + 右子树)。绝大多数题目(深度、对称、翻转、LCA)都可以用简洁的递归解决。
    • 前序:处理根 -> 左 -> 右(适合复制、翻转)。
    • 中序:左 -> 处理根 -> 右(适合 BST 验证、排序)。
    • 后序:左 -> 右 -> 处理根(适合计算深度、LCA,因为需要先知道子树信息)。
  • 层次遍历 (BFS):利用队列 (Queue),按层处理节点。适用于求层序遍历、最小深度、宽度等。
  • BST 特性:对于二叉搜索树,利用 左 < 根 < 右 的特性进行剪枝或验证。

📝 本类题型总结

题目 核心技巧 关键差异点 (vs 最大深度)
104. 最大深度 基础 DFS 基准1 + max(left, right)
101. 对称二叉树 双节点 DFS 函数传 p, q交叉比较 (p.left vs q.right)。
226. 翻转二叉树 交换 + DFS 先交换当前左右孩子,再递归。
102. 层序遍历 BFS + 队列 用队列。利用 size 分层处理
98. 验证 BST 带边界 DFS 递归需传 min, max。判断 min < val < max
236. 最近公共祖先 后序 DFS 左右返回值都不空时,当前即为答案;否则返回非空的那个。

🏆 母题:LeetCode 104. 二叉树的最大深度 (Maximum Depth of Binary Tree)

题目内容:给定一个二叉树 root ,返回其最大深度。
核心逻辑

  1. 终止条件:节点为空,深度为 0。
  2. 递归关系:当前深度 = 1 + max(左子树深度, 右子树深度)
  3. 遍历顺序:后序遍历(先算出左右子树深度,再计算当前)。
class Solution {
    public int maxDepth(TreeNode root) {
        // ⚠️ [终止条件] 空节点深度为 0
        if (root == null) return 0;
        
        // ⚠️ [核心逻辑] 递归计算左右子树深度,取最大值 + 1
        int leftDepth = maxDepth(root.left);
        int rightDepth = maxDepth(root.right);
        
        return 1 + Math.max(leftDepth, rightDepth);
    }
}

🔄 变体 1:LeetCode 101. 对称二叉树 (Symmetric Tree)

题目内容:给你一个二叉树的根节点 root , 检查它是否轴对称。
相比母题的变化

  1. 递归函数签名:需要传入两个节点 check(p, q),分别代表左子树和右子树的对应节点。
  2. 判断逻辑
    • pq 都为空:对称。
    • 若只有一个为空 或 p.val != q.val:不对称。
    • 核心递归p 的左孩子 必须等于 q 的右孩子,且 p 的右孩子 必须等于 q 的左孩子(交叉比较)。
class Solution {
    public boolean isSymmetric(TreeNode root) {
        if (root == null) return true;
        return check(root.left, root.right);
    }
    
    // ⚠️ [变化点 1] 辅助函数接收两个节点
    private boolean check(TreeNode p, TreeNode q) {
        // 都为空,对称
        if (p == null && q == null) return true;
        // 一个为空或值不等,不对称
        if (p == null || q == null || p.val != q.val) return false;
        
        // ⚠️ [变化点 2] 交叉递归比较
        // 左子的左 vs 右子的右,且 左子的右 vs 右子的左
        return check(p.left, q.right) && check(p.right, q.left);
    }
}

🔄 变体 2:LeetCode 226. 翻转二叉树 (Invert Binary Tree)

题目内容:给你一棵二叉树的根节点 root ,翻转这棵二叉树,并返回其根节点。
相比母题的变化

  1. 操作时机前序遍历(先交换当前节点的左右孩子,再递归子树)或 后序遍历 均可。通常前序更直观。
  2. 核心动作swap(root.left, root.right)
class Solution {
    public TreeNode invertTree(TreeNode root) {
        if (root == null) return null;
        
        // ⚠️ [变化点 1] 交换当前节点的左右子节点
        TreeNode temp = root.left;
        root.left = root.right;
        root.right = temp;
        
        // ⚠️ [变化点 2] 递归翻转左右子树
        invertTree(root.left);
        invertTree(root.right);
        
        return root;
    }
}

🔄 变体 3:LeetCode 102. 二叉树的层序遍历 (Binary Tree Level Order Traversal)

题目内容:给你二叉树的根节点 root ,返回其节点值的 层序遍历 (即逐层地,从左到右访问所有节点)。
相比母题的变化

  1. 数据结构:使用 队列 (Queue) 实现 BFS,而非递归栈。
  2. 分层逻辑
    • 每次循环开始前,记录当前队列大小 size = queue.size(),这就是当前层的节点数
    • 内部循环 size 次,弹出节点并将下一层节点入队。
    • 将每一层的结果存入一个列表,最后加入大列表。
import java.util.*;

class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> result = new ArrayList<>();
        if (root == null) return result;
        
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        
        while (!queue.isEmpty()) {
            // ⚠️ [变化点 1] 获取当前层的节点数量
            int size = queue.size();
            List<Integer> level = new ArrayList<>();
            
            // ⚠️ [变化点 2] 只处理当前层的 size 个节点
            for (int i = 0; i < size; i++) {
                TreeNode node = queue.poll();
                level.add(node.val);
                
                if (node.left != null) queue.offer(node.left);
                if (node.right != null) queue.offer(node.right);
            }
            
            result.add(level);
        }
        
        return result;
    }
}

🔄 变体 4:LeetCode 98. 验证二叉搜索树 (Validate Binary Search Tree)

题目内容:给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。
有效 BST 定义

  1. 节点的左子树只包含 小于 当前节点的数。
  2. 节点的右子树只包含 大于 当前节点的数。
  3. 所有左子树和右子树自身必须也是二叉搜索树。
    相比母题的变化
  4. 陷阱:不能只比较 root.val 和直接左右孩子(如 root.left.val < root.val),必须保证左子树所有节点都小于根,右子树所有节点都大于根。
  5. 递归参数:需要传递 上下界 (min, max)
    • 初始调用:isValid(root, Long.MIN_VALUE, Long.MAX_VALUE)
    • 左子树递归:上界变为 root.val -> isValid(left, min, root.val)
    • 右子树递归:下界变为 root.val -> isValid(right, root.val, max)
class Solution {
    public boolean isValidBST(TreeNode root) {
        return validate(root, Long.MIN_VALUE, Long.MAX_VALUE);
    }
    
    // ⚠️ [变化点 1] 增加 min 和 max 边界参数
    private boolean validate(TreeNode node, long min, long max) {
        if (node == null) return true;
        
        // ⚠️ [变化点 2] 检查当前节点是否在合法范围内 (开区间)
        if (node.val <= min || node.val >= max) return false;
        
        // ⚠️ [变化点 3] 递归时更新边界
        // 左子树:最大值不能超过 node.val
        // 右子树:最小值不能小于 node.val
        return validate(node.left, min, node.val) && 
               validate(node.right, node.val, max);
    }
}

🔄 变体 5:LeetCode 236. 二叉树的最近公共祖先 (Lowest Common Ancestor)

题目内容:给定一个二叉树, 找到该树中两个指定节点 pq 的最近公共祖先。
相比母题的变化

  1. 遍历顺序后序遍历(左右根)。因为我们需要知道左右子树是否包含了 pq,才能决定当前节点是不是 LCA。
  2. 逻辑判断
    • 若当前节点是 pq,直接返回当前节点。
    • 递归左右子树,得到 leftright
    • 情况 Aleftright 都不为空 -> 说明 pq 分别在两侧,当前节点即为 LCA
    • 情况 Bleft 不为空,right 为空 -> 说明 p, q 都在左子树,返回 left
    • 情况 Cright 不为空,left 为空 -> 说明 p, q 都在右子树,返回 right
class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        // ⚠️ [终止条件] 遇到空节点,或遇到 p/q,直接返回
        if (root == null || root == p || root == q) {
            return root;
        }
        
        // ⚠️ [变化点 1] 后序遍历:先查左右
        TreeNode left = lowestCommonAncestor(root.left, p, q);
        TreeNode right = lowestCommonAncestor(root.right, p, q);
        
        // ⚠️ [变化点 2] 核心判断逻辑
        if (left != null && right != null) {
            // p 和 q 分别在左右两侧,当前 root 就是 LCA
            return root;
        }
        
        // 哪边非空就返回哪边(如果都空则返回 null)
        return (left != null) ? left : right;
    }
}
posted @ 2026-03-08 21:53  scales123  阅读(56)  评论(0)    收藏  举报