前言
大家刷二叉树高频题时,对称二叉树绝对是入门必做经典题,也是面试高频考点。这道题能很好考察我们对二叉树遍历、镜像逻辑的理解,今天用 JavaScript 两种思路完整拆解,附带清晰图解、边界分析,看完直接吃透。
题目描述
给你一个二叉树的根节点 root ,检查它是否轴对称。
示例 1:
plaintext
markdown
1
/ \
2 2
/ \ / \
3 4 4 3
输入:root = [1,2,3,4,4,3]输出:true
示例 2:
plaintext
markdown
1
/ \
2 2
\ \
3 3
输入:root = [1,2,2,null,3,null,3]输出:false
核心解题思路
一棵树轴对称,本质转化为:根节点的左子树 和 根节点的右子树 互为镜像二叉树。那两棵树互为镜像的判断规则是什么?我们整理 3 条核心条件:
-
两棵树当前节点都为空 → 匹配成功;
-
其中一个节点为空、另一个不为空 → 直接不对称;
-
两个节点值相等,且交叉对比子节点:
- 左树左孩子 ↔ 右树右孩子
- 左树右孩子 ↔ 右树左孩子
重点:普通相同二叉树是「左对左、右对右」,镜像二叉树必须「左对右、右对左」,这是本题最容易踩坑的点。
解法一:递归(最简直观,推荐入门优先掌握)
完整代码
javascript
运行
kotlin
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {boolean}
*/
var isSymmetric = function(root) {
// 辅助函数:接收两棵树的根,判断是否镜像
const check = (p, q) => {
// 情况1:两个节点都为空,对称
if (!p && !q) return true;
// 情况2:一个空一个非空,不对称
if (!p || !q) return false;
// 情况3:值相等 + 交叉递归校验子树
return p.val === q.val
&& check(p.left, q.right)
&& check(p.right, q.left);
}
// 空树直接返回true,否则对比根的左右子树
return root ? check(root.left, root.right) : true;
};
代码分步解析
- **辅助函数
check(p,q)**专门用来对比两棵子树是否镜像,完全遵循上面三条判断逻辑,递归逐层向下校验。 - 入口函数处理边界 如果
root是null(空二叉树),天然对称直接返回true;否则把根的左子树、右子树传入check做镜像对比。
递归执行流程(示例 1 演示)
- 初始调用
check(2, 2),值相等; - 第一层递归:
check(3,3)、check(4,4); - 3、4 节点各自的左右子节点全是
null,全部返回 true; - 所有条件全部成立,最终结果为
true。
递归优缺点
✅ 优点:代码极简、逻辑清晰,一行就能看懂核心镜像规则,适合刷题快速写出;❌ 缺点:当二叉树深度极大时,会出现递归栈溢出,深度过深测试用例无法通过。
解法二:迭代 BFS 队列实现(无栈溢出,工程稳妥)
为了解决递归深度溢出问题,我们改用广度优先遍历,用队列存储成对需要对比的节点,逐层校验。
完整代码
javascript
运行
kotlin
var isSymmetric = function(root) {
// 空树直接对称
if (!root) return true;
// 队列成对存放待对比节点
const queue = [root.left, root.right];
while (queue.length) {
// 每次取出一对节点
let leftNode = queue.shift();
let rightNode = queue.shift();
// 两者都为空,无需校验,跳过本次循环
if (!leftNode && !rightNode) continue;
// 一空一非空 / 值不相等,直接不对称
if (!leftNode || !rightNode || leftNode.val !== rightNode.val) {
return false;
}
// 成对入队,保持交叉对比顺序
queue.push(leftNode.left, rightNode.right);
queue.push(leftNode.right, rightNode.left);
}
// 全部节点校验完成,对称
return true;
};
迭代核心逻辑
- 初始化队列时,直接放入根的左、右子树,保证每次处理一对节点;
- 循环不断取出队首两个节点做校验;
- 校验通过后,按照「左左配右右、左右配右左」的交叉顺序把子节点入队;
- 只要任意一对不满足镜像规则,立刻返回
false;队列遍历完成无异常则返回true。
迭代优缺点
✅ 优点:循环实现,不存在递归调用栈上限,超大深度二叉树也能正常运行;❌ 缺点:代码量比递归稍多,需要手动维护队列顺序,容易写错入队顺序。
常见踩坑点总结
- 子节点对比顺序写反 错误写法:
check(p.left, q.left) && check(p.right, q.right)后果:只会判断两棵树是否完全相同,而非镜像,示例 1 能过,但示例 2 会出错。 - 忘记处理
root === null空树边界题目规定空二叉树是对称,缺少判断会直接报错。 - 迭代时入队顺序混乱必须成对
(左左,右右)、(左右,右左)依次入队,打乱顺序会导致对比节点不匹配。
两种解法复杂度分析
-
时间复杂度:(O(n))n 为二叉树节点总数,两种方法每个节点只会访问一次。
-
空间复杂度:
- 递归:(O(n)),最坏情况树退化成链表,递归栈占用全部节点;
- 迭代 BFS:(O(n)),队列最多存储一层节点,满二叉树底层节点约占 (n/2)。
总结
- 面试快速写题、入门学习优先选递归解法,逻辑简单不容易出错;
- 数据量大、树深度极高场景,选择队列迭代 BFS避免栈溢出;
- 镜像二叉树核心记忆点:交叉对比子节点,左配右、右配左。
这道题是二叉树镜像类题目的母题,吃透后,同类型「翻转二叉树」「判断两棵树是否镜像」都能一通百通。