目录
[一、100. 相同的树](#一、100. 相同的树)
[重点 & 难点](#重点 & 难点)
[二、101. 对称二叉树:"镜像问题"](#二、101. 对称二叉树:“镜像问题”)
[方法 1:递归(辅助函数比较两棵子树)](#方法 1:递归(辅助函数比较两棵子树))
[方法 2:迭代(队列成对存储镜像节点)](#方法 2:迭代(队列成对存储镜像节点))
[重点 & 难点](#重点 & 难点)
[三、110. 平衡二叉树:后序遍历的 "剪枝"](#三、110. 平衡二叉树:后序遍历的 “剪枝”)
[实现方法(优化版:后序遍历 + 剪枝)](#实现方法(优化版:后序遍历 + 剪枝))
[重点 & 难点](#重点 & 难点)
[四、199. 二叉树的右视图:遍历顺序的 "状态控制"](#四、199. 二叉树的右视图:遍历顺序的 “状态控制”)
[核心思想:层序遍历 / 优先右子树的 DFS](#核心思想:层序遍历 / 优先右子树的 DFS)
[方法 1:层序遍历(直观易理解)](#方法 1:层序遍历(直观易理解))
[方法 2:DFS(优先右子树)](#方法 2:DFS(优先右子树))
[重点 & 难点](#重点 & 难点)
[1. 递归分治:拆解子问题](#1. 递归分治:拆解子问题)
[2. 遍历控制:记录状态](#2. 遍历控制:记录状态)
本文会拆解 LeetCode 中 4 道二叉树高频题 ------100. 相同的树、101. 对称二叉树、110. 平衡二叉树、199. 二叉树的右视图,从核心思想、实现方法到重点难点做深入分析。
一、100. 相同的树
问题描述
给定两棵二叉树的根节点 p 和 q,判断二者是否结构相同且对应节点值相等。
核心思想:递归分治
树的递归结构决定了 "整棵树相同" 可以拆解为 3 个子问题:
- 当前节点的值相等;
p的左子树与q的左子树相同;p的右子树与q的右子树相同。
实现方法
递归的关键是处理边界条件(空节点是二叉树递归的 "必考题"):
碰到递归问题,先从边界条件出发,慢慢推导
java
class Solution {
public boolean isSameTree(TreeNode p, TreeNode q) {
// 边界1:两棵树都空,相同
if (p == null && q == null) return true;
// 边界2:其中一棵空、另一棵非空,不同
if (p == null || q == null) return false;
// 子问题:值相等 + 左子树相同 + 右子树相同
return p.val == q.val
&& isSameTree(p.left, q.left)
&& isSameTree(p.right, q.right);
}
}
重点 & 难点
- 重点:递归的 "分治拆解"------ 把整树问题拆成 "当前节点 + 左右子树" 的子问题;
- 难点:容易忽略 "一个空、一个非空" 的边界条件,导致空指针异常或逻辑错误。
二、101. 对称二叉树:"镜像问题"
问题描述
给定一棵二叉树的根节点 root,判断其是否是镜像对称的(即左右子树呈镜像结构)。
核心思想:转化为 "两棵树的镜像比较"
"一棵树对称" 等价于 "它的左子树和右子树互为镜像",而 "两棵树镜像" 的子问题是:
- 树 A 的根节点值 = 树 B 的根节点值;
- 树 A 的左子树 与 树 B 的右子树 镜像;
- 树 A 的右子树 与 树 B 的左子树 镜像。
实现方法
方法 1:递归(辅助函数比较两棵子树)
java
class Solution {
public boolean isSymmetric(TreeNode root) {
// 空树是对称的;否则比较左、右子树是否镜像
return root == null ? true : isMirror(root.left, root.right);
}
// 辅助函数:判断两棵树是否镜像
private boolean isMirror(TreeNode a, TreeNode b) {
if (a == null && b == null) return true;
if (a == null || b == null) return false;
// 子问题:值相等 + 左对右、右对左
return a.val == b.val
&& isMirror(a.left, b.right)
&& isMirror(a.right, b.left);
}
}
方法 2:迭代(队列成对存储镜像节点)
用队列将 "需要比较的镜像节点" 成对入队,每次取出一对节点校验:
java
public boolean isSymmetric(TreeNode root) {
if (root == null) return true;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root.left);
queue.offer(root.right);
while (!queue.isEmpty()) {
TreeNode a = queue.poll();
TreeNode b = queue.poll();
// 处理空节点的情况
if (a == null && b == null) continue;
if (a == null || b == null) return false;
if (a.val != b.val) return false;
// 成对入队镜像节点:a左对b右,a右对b左
queue.offer(a.left);
queue.offer(b.right);
queue.offer(a.right);
queue.offer(b.left);
}
return true;
}
重点 & 难点
- 重点:问题转化 ------ 将 "一棵树的对称" 转化为 "两棵子树的镜像比较",是递归的关键;
- 难点:镜像节点的对应关系(左对右、右对左)容易搞反,导致逻辑错误。
三、110. 平衡二叉树:后序遍历的 "剪枝"
问题描述
判断一棵二叉树是否是平衡二叉树(AVL 树):每个节点的左右子树高度差的绝对值不超过 1。
核心思想:后序遍历 + 剪枝
平衡二叉树需要先获取子树高度,再判断当前节点的平衡度------ 后序遍历(左→右→根)恰好符合 "先处理子树、再处理根" 的需求。
同时,为了避免重复计算子树高度(基础版会重复计算),我们可以在计算高度的同时集成平衡判断 :若某子树不平衡,直接返回标记值(如 -1),提前终止递归(剪枝)。
实现方法(优化版:后序遍历 + 剪枝)
java
class Solution {
public boolean isBalanced(TreeNode root) {
// 若返回-1则不平衡,否则平衡
return getHeightAndCheck(root) != -1;
}
// 辅助函数:计算高度 + 检查平衡,不平衡返回-1
private int getHeightAndCheck(TreeNode node) {
if (node == null) return 0; // 空节点高度为0
// 先检查左子树
int leftHeight = getHeightAndCheck(node.left);
if (leftHeight == -1) return -1;
// 再检查右子树
int rightHeight = getHeightAndCheck(node.right);
if (rightHeight == -1) return -1;
// 检查当前节点的平衡因子
if (Math.abs(leftHeight - rightHeight) > 1) return -1;
// 平衡则返回当前节点高度
return Math.max(leftHeight, rightHeight) + 1;
}
}
重点 & 难点
- 重点:后序遍历的应用场景(需要子树信息的问题)、剪枝技巧(避免无效计算);
- 难点:将 "高度计算" 与 "平衡判断" 合并,需要清晰的递归返回值逻辑(高度 / 不平衡标记)。
四、199. 二叉树的右视图:遍历顺序的 "状态控制"
问题描述
从二叉树的右侧观察,返回从顶部到底部能看到的节点值(即每一层的最后一个节点)。
核心思想:层序遍历 / 优先右子树的 DFS
- 层序遍历(BFS):记录每一层的节点数,遍历完一层后,取最后一个节点;
- 深度优先遍历(DFS):优先访问右子树,记录当前深度,当深度等于结果列表长度时,说明是该层的 "第一个可见节点"(因先右,所以是最右侧)。
实现方法
方法 1:层序遍历(直观易理解)
java
class Solution {
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 levelSize = queue.size(); // 当前层的节点数
for (int i = 0; i < levelSize; i++) {
TreeNode node = queue.poll();
// 若为当前层最后一个节点,加入结果
if (i == levelSize - 1) {
result.add(node.val);
}
// 左、右子节点入队(保证下一层的顺序)
if (node.left != null) queue.offer(node.left);
if (node.right != null) queue.offer(node.right);
}
}
return result;
}
}
方法 2:DFS(优先右子树)
java
class Solution {
public List<Integer> rightSideView(TreeNode root) {
List<Integer> result = new ArrayList<>();
dfs(root, 0, result);
return result;
}
private void dfs(TreeNode node, int depth, List<Integer> result) {
if (node == null) return;
// 深度 == 结果长度 → 该层第一个节点(先右,故为最右侧)
if (depth == result.size()) {
result.add(node.val);
}
// 优先访问右子树,深度+1
dfs(node.right, depth + 1, result);
dfs(node.left, depth + 1, result);
}
}
重点 & 难点
- 重点:通过 "层大小"(BFS)或 "深度"(DFS)控制 "每一层的可见节点";
- 难点:DFS 中 "深度与结果列表长度的对应关系"------ 需要理解 "先右子树" 带来的层节点顺序。
总结:二叉树问题的通用解题逻辑
这 4 道题覆盖了二叉树的核心解题思路,总结为 2 个关键词:
1. 递归分治:拆解子问题
二叉树的递归问题,核心是找到 "整树→子树" 的拆解逻辑,同时必须处理空节点的边界条件:
- 相同的树:整树相同 → 节点值 + 左子树 + 右子树相同;
- 对称二叉树:整树对称 → 左子树与右子树镜像。
2. 遍历控制:记录状态
需要按顺序访问节点或记录层 / 深度信息时,用遍历(BFS/DFS)+ 状态变量(层大小、深度):
- 平衡二叉树:后序遍历(先子树后根)+ 高度状态;
- 右视图:层序遍历(层大小)/ DFS(深度)+ 可见节点状态。
模板使用总结
| 模板类型 | 核心适用场景 | 关键技巧 |
|---|---|---|
| 递归分治 | 整树→子树拆解、需子树信息 | 优先处理空节点、剪枝(提前终止) |
| BFS 层序 | 按层处理、找层内特征 | 记录每层节点数、控制层边界 |
| DFS 遍历 | 路径记录、深度相关、遍历顺序 | 状态变量传递(深度 / 路径)、调整遍历顺序 |
快速选型技巧
-
看到 "平衡、相同、对称、最大深度"→ 用递归分治模板;
-
看到 "按层、右视图、左下角、层最大值"→ 用BFS 模板;
-
看到 "前 / 中 / 后序遍历、路径、深度优先"→ 用DFS 模板 。
