算法面试突击手册 —— 二叉树(DFS/BFS)


一、概念与特点

大白话 + 类比

二叉树 = 家族谱系图

每个节点有两个分支(左孩子、右孩子),就像一个人的两个孩子。递归是最自然的思维方式——你想统计整个家族的人数,不需要自己一个一个数,而是问左孩子”你家多少人”,再问右孩子”你家多少人”,加自己就是答案。这就是后序遍历的直觉。

核心遍历方式

遍历顺序类比典型应用
前序根→左→右先自己,再孩子序列化、构造树
中序左→根→右BST = 有序输出验证 BST、第 K 小
后序左→右→根先孩子,后自己求深度/直径、最大路径和
层序(BFS)一层一层排队入场右视图、最大宽度

核心特征(什么时候该想到用树)

特征说明
递归结构树天然递归,左子树+右子树+根
遍历/搜索DFS(前中后序)、BFS(层序)
子树/路径自底向上聚合信息(后序)
BST 性质左 < 根 < 右,中序有序

识别信号词

  • “二叉树”、“结点” → 直接是树题
  • “深度”、“高度”、“直径” → 后序遍历
  • “层序”、“按层”、“右视图” → BFS(队列)
  • “二叉搜索树”、“BST” → 中序遍历 + BST 性质
  • “路径”、“路径和” → DFS 后序遍历

二、应用场景

两种遍历模板

// DFS(递归后序)
int dfs(TreeNode root) {
    if (root == null) return 0;
    int left = dfs(root.left);
    int right = dfs(root.right);
    return 1 + Math.max(left, right); // 例:求高度
}

// BFS(层序)
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
    int size = queue.size();
    for (int i = 0; i < size; i++) {
        TreeNode node = queue.poll();
        // 处理当前节点
        if (node.left != null) queue.offer(node.left);
        if (node.right != null) queue.offer(node.right);
    }
}

三、Hot 100 精讲题目


104. Maximum Depth of Binary Tree 二叉树的最大深度

📌 求二叉树的最大深度(根到最远叶子的节点数)。如 [3,9,20,null,null,15,7] → 3

🔍 解题分析

核心思路(后序):树的最大深度 = 1 + max(左子树深度, 右子树深度)。空树深度为 0。

✅ 完整 Java 实现

public int maxDepth(TreeNode root) {
    if (root == null) return 0;
    int leftDepth = maxDepth(root.left);
    int rightDepth = maxDepth(root.right);
    return 1 + Math.max(leftDepth, rightDepth);
}

💡 记忆口诀

“空树为 0,非空 = 1 + 左右深的较大者”


94. Binary Tree Inorder Traversal 二叉树的中序遍历

📌 返回二叉树的中序遍历结果。

✅ 完整 Java 实现

// 递归
public List<Integer> inorderTraversal(TreeNode root) {
    List<Integer> result = new ArrayList<>();
    inorder(root, result);
    return result;
}

private void inorder(TreeNode node, List<Integer> result) {
    if (node == null) return;
    inorder(node.left, result);
    result.add(node.val);
    inorder(node.right, result);
}

// 迭代(栈模拟)
public List<Integer> inorderTraversal_iter(TreeNode root) {
    List<Integer> result = new ArrayList<>();
    Deque<TreeNode> stack = new ArrayDeque<>();
    TreeNode cur = root;

    while (cur != null || !stack.isEmpty()) {
        // 一路向左走到底
        while (cur != null) {
            stack.push(cur);
            cur = cur.left;
        }
        cur = stack.pop();   // 左到底了,弹出根
        result.add(cur.val); // 访问根
        cur = cur.right;     // 转向右子树
    }

    return result;
}

💡 记忆口诀

“一路向左走到底,弹出来访问后转到右边”


226. Invert Binary Tree 翻转二叉树

📌 翻转二叉树,镜像每一层的左右子树。

🔍 解题分析

核心思路(前序/后序都可以):交换当前节点的左右孩子,然后递归翻转左右子树。

✅ 完整 Java 实现

public TreeNode invertTree(TreeNode root) {
    if (root == null) return null;

    // 交换左右孩子
    TreeNode temp = root.left;
    root.left = root.right;
    root.right = temp;

    // 递归翻转子树
    invertTree(root.left);
    invertTree(root.right);

    return root;
}

💡 记忆口诀

“左右互换,递归到底”


101. Symmetric Tree 对称二叉树

📌 判断二叉树是否镜像对称。

🔍 解题分析

核心思路:双指针同时递归。p 走左子树时 q 走右子树,递归比较 p.left vs q.right 以及 p.right vs q.left。

✅ 完整 Java 实现

public boolean isSymmetric(TreeNode root) {
    if (root == null) return true;
    return isMirror(root.left, root.right);
}

private boolean isMirror(TreeNode p, TreeNode q) {
    if (p == null && q == null) return true;
    if (p == null || q == null) return false;
    // 值相等 + p 的左 vs q 的右 + p 的右 vs q 的左
    return p.val == q.val
        && isMirror(p.left, q.right)
        && isMirror(p.right, q.left);
}

💡 记忆口诀

“两个指针镜像走,值相等且交叉递归”


543. Diameter of Binary Tree 二叉树的直径

📌 直径定义为任意两节点间的最长路径长度(边数)。可能不经过根。

🔍 解题分析

核心思路(后序):对每个节点,经过它的最长路径 = 左子树深度 + 右子树深度。全局最大直径就是所有节点的这个值中的最大值。

关键:深度和直径是两个概念。返回深度给父节点,用深度算直径并更新全局答案。这是后序遍历的经典模式:返回一个值,更新另一个值

✅ 完整 Java 实现

private int maxDiameter = 0;

public int diameterOfBinaryTree(TreeNode root) {
    maxDepth(root);
    return maxDiameter;
}

// 返回以 node 为根的子树深度,同时更新全局直径
private int maxDepth(TreeNode node) {
    if (node == null) return 0;

    int left = maxDepth(node.left);
    int right = maxDepth(node.right);

    // 经过当前节点的直径 = 左深 + 右深
    maxDiameter = Math.max(maxDiameter, left + right);

    // 返回深度给父节点
    return 1 + Math.max(left, right);
}

💡 记忆口诀

“后序求深度,左右深度和更新直径”


102. Binary Tree Level Order Traversal 二叉树的层序遍历

📌 按层返回节点的值列表。如 [3,9,20,null,null,15,7][[3],[9,20],[15,7]]

🔍 解题分析

核心思路(BFS + 队列):用队列存储当前层的节点。每轮处理队列中全部节点(即当前层的所有节点),同时把下一层节点入队。

✅ 完整 Java 实现

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()) {
        int levelSize = queue.size(); // 当前层的节点数
        List<Integer> level = new ArrayList<>();

        for (int i = 0; i < levelSize; 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;
}

💡 记忆口诀

“队列存当前层,size 记录该层几个”


108. Convert Sorted Array to BST 有序数组转二叉搜索树

📌 将一个升序数组转换成高度平衡的 BST。

🔍 解题分析

核心思路:二分法选根。数组中间元素做根,左边子数组构造左子树,右边子数组构造右子树。递归完成。

✅ 完整 Java 实现

public TreeNode sortedArrayToBST(int[] nums) {
    return buildBST(nums, 0, nums.length - 1);
}

private TreeNode buildBST(int[] nums, int left, int right) {
    if (left > right) return null;

    int mid = left + (right - left) / 2; // 中间位置做根
    TreeNode root = new TreeNode(nums[mid]);

    root.left = buildBST(nums, left, mid - 1);
    root.right = buildBST(nums, mid + 1, right);

    return root;
}

💡 记忆口诀

“中间做根,左边造左树,右边造右树”


98. Validate Binary Search Tree 验证二叉搜索树

📌 判断一棵二叉树是否为有效的 BST。

🔍 解题分析

方法一:递归传范围

  • 每个节点必须在一个合法区间内
  • 左子节点区间:(min, root.val)
  • 右子节点区间:(root.val, max)

方法二:中序遍历

  • BST 的中序遍历是严格递增序列
  • 维护一个 pre 记录前一个访问的节点值

✅ 完整 Java 实现

// 方法一:递归传范围(推荐)
public boolean isValidBST(TreeNode root) {
    return validate(root, Long.MIN_VALUE, Long.MAX_VALUE);
}

private boolean validate(TreeNode node, long min, long max) {
    if (node == null) return true;
    if (node.val <= min || node.val >= max) return false;
    // 左子树区间:(min, node.val),右子树区间:(node.val, max)
    return validate(node.left, min, node.val)
        && validate(node.right, node.val, max);
}

// 方法二:中序遍历
private TreeNode prev = null;

public boolean isValidBST_inorder(TreeNode root) {
    if (root == null) return true;
    if (!isValidBST_inorder(root.left)) return false;
    if (prev != null && prev.val >= root.val) return false;
    prev = root;
    return isValidBST_inorder(root.right);
}

💡 记忆口诀

“每个节点在区间内,左紧右紧递归传”


230. Kth Smallest Element in a BST 二叉搜索树中第 K 小的元素

📌 找到 BST 中第 K 小的元素。

🔍 解题分析

核心思路:BST 中序遍历即升序。中序遍历到第 K 个节点时返回。

✅ 完整 Java 实现

private int count = 0;
private int answer = 0;

public int kthSmallest(TreeNode root, int k) {
    count = k;
    inorder(root);
    return answer;
}

private void inorder(TreeNode node) {
    if (node == null) return;
    inorder(node.left);
    count--;
    if (count == 0) {
        answer = node.val;
        return;
    }
    inorder(node.right);
}

💡 记忆口诀

“中序遍历走起,数到第 K 个就停下”


199. Binary Tree Right Side View 二叉树的右视图

📌 返回从右侧看到的节点值(每层最右边的节点)。

🔍 解题分析

核心思路(BFS):层序遍历,每层最后一个节点就是右视图看到的节点。

✅ 完整 Java 实现

public List<Integer> rightSideView(TreeNode root) {
    List<Integer> result = new ArrayList<>();
    if (root == null) return result;

    Queue<TreeNode> queue = new LinkedList<>();
    queue.offer(root);

    while (!queue.isEmpty()) {
        int size = queue.size();
        for (int i = 0; i < size; i++) {
            TreeNode node = queue.poll();
            if (i == size - 1) result.add(node.val); // 每层最后一个
            if (node.left != null) queue.offer(node.left);
            if (node.right != null) queue.offer(node.right);
        }
    }

    return result;
}

💡 记忆口诀

“层序遍历,每层收最后一个”


114. Flatten Binary Tree to Linked List 二叉树展开为链表

📌 将二叉树原地展开为单链表(右指针做 next),顺序与前序遍历相同。

🔍 解题分析

核心思路(后序):从下往上摊平。先把左右子树都展平,然后:左链表接到 root.right,右链表接到左链表末端。

✅ 完整 Java 实现

public void flatten(TreeNode root) {
    if (root == null) return;

    // 后序:先展平左右
    flatten(root.left);
    flatten(root.right);

    // 暂存左右
    TreeNode left = root.left;
    TreeNode right = root.right;

    // 左子树移到右边
    root.left = null;
    root.right = left;

    // 找到当前右链的末端,把原来的右子树接上去
    TreeNode cur = root;
    while (cur.right != null) {
        cur = cur.right;
    }
    cur.right = right;
}

💡 记忆口诀

“后序展平左右,左挂右,右挂左链尾”


105. Construct Binary Tree from Preorder and Inorder 从前序与中序构造二叉树

📌 给定前序和中序遍历结果,构造二叉树。

🔍 解题分析

核心思路:前序第一个是根。在中序中找到根的位置,左边是左子树,右边是右子树。递归构造。

✅ 完整 Java 实现

// 用 HashMap 记录中序遍历中 值→下标,快速定位根的位置
private Map<Integer, Integer> inorderMap = new HashMap<>();
private int preIdx = 0; // 当前前序中的位置

public TreeNode buildTree(int[] preorder, int[] inorder) {
    for (int i = 0; i < inorder.length; i++) {
        inorderMap.put(inorder[i], i);
    }
    return build(preorder, 0, inorder.length - 1);
}

private TreeNode build(int[] preorder, int inLeft, int inRight) {
    if (inLeft > inRight) return null;

    int rootVal = preorder[preIdx++]; // 前序第一个是根
    TreeNode root = new TreeNode(rootVal);

    int rootInIdx = inorderMap.get(rootVal); // 根在中序的位置

    root.left = build(preorder, inLeft, rootInIdx - 1);  // 左子树
    root.right = build(preorder, rootInIdx + 1, inRight); // 右子树

    return root;
}

💡 记忆口诀

“前序第一个是根,中序找根分左右”


236. Lowest Common Ancestor 二叉树的最近公共祖先

📌 找两个指定节点的最近公共祖先。

🔍 解题分析

核心思路(后序):自底向上查找。如果当前节点是 p 或 q 之一,返回当前节点。否则看左右子树:如果左右各返回了一个非 null(说明 p 和 q 分别在左右),当前节点就是 LCA;如果只有一边非 null,返回那一边。

✅ 完整 Java 实现

public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
    if (root == null || root == p || root == q) {
        return root; // 找到了 p 或 q,或者到底了
    }

    TreeNode left = lowestCommonAncestor(root.left, p, q);
    TreeNode right = lowestCommonAncestor(root.right, p, q);

    // 左右各有一个 → 当前节点就是 LCA
    if (left != null && right != null) return root;

    // 只有一边有 → 返回那边往上传递
    return left != null ? left : right;
}

💡 记忆口诀

“左右各找到一个,我就是祖先;只有一边有,往上传递”


124. Binary Tree Maximum Path Sum 二叉树中的最大路径和

📌 求任意一条节点序列(至少一个节点)的最大路径和。路径可以不经过根。

🔍 解题分析

核心思路(后序,类似 543 直径):对每个节点,计算经过它的最大路径和 = root.val + max(0, 左子树贡献) + max(0, 右子树贡献)。返回给父节点的是 root.val + max(0, max(左贡献, 右贡献))

关键:负数不取(用 max(0, …))。贡献和路径和是两个值,返回一个,更新另一个。

✅ 完整 Java 实现

private int maxSum = Integer.MIN_VALUE;

public int maxPathSum(TreeNode root) {
    maxGain(root);
    return maxSum;
}

// 返回以 node 为起点向下延伸的最大单边贡献
private int maxGain(TreeNode node) {
    if (node == null) return 0;

    // 左右子树贡献,负的就不要
    int leftGain = Math.max(0, maxGain(node.left));
    int rightGain = Math.max(0, maxGain(node.right));

    // 经过当前节点的最大路径和
    int pathSum = node.val + leftGain + rightGain;
    maxSum = Math.max(maxSum, pathSum);

    // 返回单边最大贡献给父节点
    return node.val + Math.max(leftGain, rightGain);
}

💡 记忆口诀

“左右贡献取正,全路径和更新最大,单边返回给父”


538. Convert BST to Greater Tree 把二叉搜索树转换为累加树

📌 使每个节点的值变成原树中大于等于它的节点值之和。

🔍 解题分析

核心思路(反向中序:右→根→左):反向中序遍历,维护一个累加和 sum。访问每个节点时 sum += node.val,然后把 node.val 设成 sum。

✅ 完整 Java 实现

private int sum = 0;

public TreeNode convertBST(TreeNode root) {
    reverseInorder(root);
    return root;
}

private void reverseInorder(TreeNode node) {
    if (node == null) return;
    reverseInorder(node.right);  // 先右边(更大值)
    sum += node.val;             // 累加
    node.val = sum;              // 更新节点值
    reverseInorder(node.left);   // 再左边(更小值)
}

💡 记忆口诀

“反着中序:右→根→左,一路累加”


617. Merge Two Binary Trees 合并二叉树

📌 合并两棵树,对应节点值相加。某节点为 null 时取另一棵树的节点。

🔍 解题分析

核心思路(同步递归):两棵树一起遍历。如果一边为 null 返回另一边;否则值相加,左右孩子递归合并。

✅ 完整 Java 实现

public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {
    if (root1 == null) return root2;
    if (root2 == null) return root1;

    root1.val += root2.val;
    root1.left = mergeTrees(root1.left, root2.left);
    root1.right = mergeTrees(root1.right, root2.right);

    return root1;
}

💡 记忆口诀

“有一边 null 取另一边,都不空就相加合并”


297. Serialize and Deserialize Binary Tree 二叉树的序列化与反序列化

📌 把二叉树编码为字符串并能解码还原。

🔍 解题分析

核心思路(前序遍历):前序序列化,null 用 “N” 标记,节点间用逗号分隔。反序列化时用队列/列表按前序重建。

✅ 完整 Java 实现

public String serialize(TreeNode root) {
    if (root == null) return "N";
    // 前序:根,左,右
    return root.val + "," + serialize(root.left) + "," + serialize(root.right);
}

public TreeNode deserialize(String data) {
    Queue<String> queue = new LinkedList<>(Arrays.asList(data.split(",")));
    return buildTree(queue);
}

private TreeNode buildTree(Queue<String> queue) {
    String val = queue.poll();
    if (val.equals("N")) return null;

    TreeNode node = new TreeNode(Integer.parseInt(val));
    node.left = buildTree(queue);
    node.right = buildTree(queue);

    return node;
}

💡 记忆口诀

“前序序列化 null 标 N,反序列化遇 N 返回 null”


🔜 上一章:08-链表 | 下一章:10-回溯