LeetCode 上的经典二叉树题目------101. 对称二叉树,这道题核心考察二叉树的遍历逻辑,也是面试中常考的基础题,今天就带大家用「递归」和「迭代」两种思路彻底搞定它,还会拆解代码里的关键细节,帮大家避开易错点。
一、题目解读
题目很简洁:给你一个二叉树的根节点 root,检查它是否轴对称。
先明确「轴对称」的定义:二叉树的左子树和右子树,沿着根节点所在的竖直线镜像对称。简单说就是,左子树的左节点 = 右子树的右节点,左子树的右节点 = 右子树的左节点,且所有对应节点的值都相等,空节点的位置也要对应。
举个例子:
-
对称的情况:根节点的左孩子和右孩子值相等,左孩子的左孩子 = 右孩子的右孩子,左孩子的右孩子 = 右孩子的左孩子(哪怕都是空节点也符合)。
-
不对称的情况:任意一对对应节点的值不相等,或空节点位置不对应(比如左子树有左节点,右子树对应位置没有右节点)。
二、前置准备:TreeNode 类定义
题目已经给出了二叉树节点的类定义,这里再贴一次,方便大家对照代码理解(TypeScript 版本):
typescript
class TreeNode {
val: number
left: TreeNode | null
right: TreeNode | null
constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
this.val = (val === undefined ? 0 : val) // 节点值默认0
this.left = (left === undefined ? null : left) // 左孩子默认null
this.right = (right === undefined ? null : right) // 右孩子默认null
}
}
三、解法一:递归(简洁高效,优先掌握)
3.1 递归思路
递归的核心是「拆分问题」:判断整棵树是否对称,等价于判断「根节点的左子树」和「根节点的右子树」是否镜像对称。
而判断两个子树是否镜像对称,需要满足 3 个条件:
-
两个子树的根节点值相等(如果都为空,也符合条件);
-
第一个子树的左孩子,和第二个子树的右孩子镜像对称;
-
第一个子树的右孩子,和第二个子树的左孩子镜像对称。
基于这个逻辑,我们可以写一个辅助函数,专门判断两个节点是否是镜像关系,再用这个辅助函数递归判断左、右子树。
3.2 递归代码实现
typescript
// 辅助函数:判断两个节点是否镜像对称
function isSymmetric_helper(node_1: TreeNode | null, node_2: TreeNode | null): boolean {
// 情况1:两个节点都为空 → 对称,返回true
if (!node_1 && !node_2) {
return true;
}
// 情况2:一个为空、一个不为空,或两个节点值不相等 → 不对称,返回false
if (!node_1 || !node_2 || node_1.val !== node_2.val) {
return false;
}
// 情况3:两个节点值相等,递归判断它们的子节点(镜像对应)
return isSymmetric_helper(node_1.left, node_2.right) && isSymmetric_helper(node_1.right, node_2.left);
}
// 主函数:判断整棵树是否对称
function isSymmetric_1(root: TreeNode | null): boolean {
// 边界条件:根节点为空 → 树对称(空树也是对称的)
if (!root) {
return true;
}
// 递归判断左子树和右子树是否镜像对称
return isSymmetric_helper(root.left, root.right);
};
3.3 关键细节(易错点)
-
边界处理:空树(root 为 null)直接返回 true,因为题目中没有说空树不对称;
-
辅助函数的终止条件:先判断「两个节点都为空」,再判断「一个空一个非空」,避免空指针异常(如果先判断值相等,空节点会报错);
-
递归传递的节点:必须是「node1.left 和 node2.right」「node1.right 和 node2.left」,这是镜像对称的核心,不能传反。
3.4 复杂度分析
时间复杂度:O(n),n 是二叉树的节点数,每个节点会被访问一次(递归一次);
空间复杂度:O(n),最坏情况是二叉树为链表(比如所有节点都在左子树),递归栈的深度会达到 n。
四、解法二:迭代(用队列实现,规避递归栈溢出)
4.1 迭代思路
迭代的思路和递归一致,都是判断「左子树和右子树是否镜像对称」,只是把递归换成了「队列遍历」。
核心逻辑:用队列存储「需要成对判断的节点」,每次从队列中取出两个节点,判断它们是否对称;然后将这两个节点的「镜像子节点」(node1.left 和 node2.right、node1.right 和 node2.left)依次加入队列,循环这个过程,直到队列为空(全部对称)或找到不对称的节点(直接返回 false)。
4.2 迭代代码实现
typescript
function isSymmetric_2(root: TreeNode | null): boolean {
// 边界条件:空树直接返回true
if (!root) {
return true;
}
// 队列:存储需要成对判断的节点,初始加入左、右子树的根节点
let queue = [root.left, root.right];
// 队列不为空,继续判断
while (queue.length) {
const size = queue.length; // 每次处理队列中所有成对的节点(一层的节点)
for (let i = 0; i < size; i++) {
// 每次取出两个节点(成对判断)
const node_1 = queue.shift(); // 取出第一个节点
const node_2 = queue.shift(); // 取出第二个节点(与第一个节点镜像对应)
// 两个节点都为空,继续判断下一对
if (!node_1 && !node_2) {
continue;
}
// 一个空、一个非空,或值不相等 → 不对称
if (!node_1 || !node_2 || node_1.val !== node_2.val) {
return false;
}
// 将镜像子节点加入队列,供下一轮判断
queue.push(node_1.left);
queue.push(node_2.right);
queue.push(node_1.right);
queue.push(node_2.left);
}
}
// 队列为空,所有节点都对称
return true;
};
3.3 关键细节(易错点)
-
队列初始化:必须同时加入 root.left 和 root.right,不能只加一个;
-
shift() 方法:队列是「先进先出」,用 shift() 取出队首元素(注意:shift() 会修改原队列,时间复杂度是 O(n),如果想优化,可以用双端队列 deque);
-
子节点入队顺序:必须是「node1.left、node2.right、node1.right、node2.left」,保证下一轮取出的两个节点依然是镜像对应关系;
-
循环终止条件:队列为空时,说明所有成对节点都判断完毕,返回 true;只要找到一对不对称节点,直接返回 false,提前终止。
4.4 复杂度分析
时间复杂度:O(n),每个节点会被加入队列一次、取出一次,总共访问 n 次;
空间复杂度:O(n),最坏情况是二叉树为满二叉树,队列中最多会存储 n/2 个节点(最后一层的节点数),整体空间复杂度为 O(n)。
五、两种解法对比 & 总结
| 解法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 递归 | 代码简洁、思路直观,容易上手 | 递归栈深度有限制,极端情况下(链表式二叉树)会栈溢出 | 二叉树深度不大,追求代码简洁 |
| 迭代 | 规避栈溢出问题,稳定性更高 | 代码稍长,需要手动维护队列 | 二叉树深度较大,追求稳定性 |
核心总结
-
对称二叉树的核心是「左子树与右子树镜像对称」,本质是「成对节点的对比」;
-
递归和迭代的思路一致,都是围绕「成对判断节点」展开,只是实现方式不同;
-
无论哪种解法,都要注意「空节点的处理」,这是最容易出错的地方;
-
面试中如果被问到这道题,建议先讲递归思路(简洁),再补充迭代思路(考虑边界情况),体现思维的完整性。
六、整体总结
本文围绕 LeetCode 101. 对称二叉树,详细拆解了「递归」和「迭代」两种核心解法,核心目标是帮助大家掌握二叉树镜像对称的判断逻辑,同时规避解题中的常见易错点,适配笔试和面试场景。
从解题逻辑来看,两种解法的核心一致------判断二叉树的左、右子树是否镜像对称,本质都是通过「成对对比对应节点」验证对称性,区别仅在于遍历节点的方式:递归依赖系统栈,代码更简洁;迭代依赖手动维护的队列,避免了递归栈溢出的风险,两种解法可根据二叉树的深度灵活选择。
解题关键提醒:无论使用哪种方法,都要优先处理空节点的边界情况,先判断「两个节点均为空」,再判断「一个空一个非空」,最后对比节点值,这样能有效避免空指针异常,这也是本题最容易失分的地方。
延伸思考:本题的解题思路可迁移到「判断两个二叉树是否镜像」「判断二叉树是否为回文结构」等类似题目中,核心都是「镜像对应节点的成对对比」,掌握本文的两种解法,能轻松应对这类二叉树拓展题目。