LeetCode 101. 对称二叉树|JS 递归 + 迭代双解法,彻底搞懂镜像判断

前言

大家刷二叉树高频题时,对称二叉树绝对是入门必做经典题,也是面试高频考点。这道题能很好考察我们对二叉树遍历、镜像逻辑的理解,今天用 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 条核心条件:

  1. 两棵树当前节点都为空 → 匹配成功;

  2. 其中一个节点为空、另一个不为空 → 直接不对称;

  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;
};

代码分步解析

  1. **辅助函数 check(p,q)**专门用来对比两棵子树是否镜像,完全遵循上面三条判断逻辑,递归逐层向下校验。
  2. 入口函数处理边界 如果 rootnull(空二叉树),天然对称直接返回 true;否则把根的左子树、右子树传入 check 做镜像对比。

递归执行流程(示例 1 演示)

  1. 初始调用 check(2, 2),值相等;
  2. 第一层递归:check(3,3)check(4,4)
  3. 3、4 节点各自的左右子节点全是 null,全部返回 true;
  4. 所有条件全部成立,最终结果为 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;
};

迭代核心逻辑

  1. 初始化队列时,直接放入根的左、右子树,保证每次处理一对节点
  2. 循环不断取出队首两个节点做校验;
  3. 校验通过后,按照「左左配右右、左右配右左」的交叉顺序把子节点入队;
  4. 只要任意一对不满足镜像规则,立刻返回 false;队列遍历完成无异常则返回 true

迭代优缺点

✅ 优点:循环实现,不存在递归调用栈上限,超大深度二叉树也能正常运行;❌ 缺点:代码量比递归稍多,需要手动维护队列顺序,容易写错入队顺序。

常见踩坑点总结

  1. 子节点对比顺序写反 错误写法:check(p.left, q.left) && check(p.right, q.right)后果:只会判断两棵树是否完全相同,而非镜像,示例 1 能过,但示例 2 会出错。
  2. 忘记处理 root === null 空树边界题目规定空二叉树是对称,缺少判断会直接报错。
  3. 迭代时入队顺序混乱必须成对 (左左,右右)(左右,右左) 依次入队,打乱顺序会导致对比节点不匹配。

两种解法复杂度分析

  1. 时间复杂度:(O(n))n 为二叉树节点总数,两种方法每个节点只会访问一次。

  2. 空间复杂度:

    • 递归:(O(n)),最坏情况树退化成链表,递归栈占用全部节点;
    • 迭代 BFS:(O(n)),队列最多存储一层节点,满二叉树底层节点约占 (n/2)。

总结

  1. 面试快速写题、入门学习优先选递归解法,逻辑简单不容易出错;
  2. 数据量大、树深度极高场景,选择队列迭代 BFS避免栈溢出;
  3. 镜像二叉树核心记忆点:交叉对比子节点,左配右、右配左

这道题是二叉树镜像类题目的母题,吃透后,同类型「翻转二叉树」「判断两棵树是否镜像」都能一通百通。

相关推荐
冬奇Lab3 小时前
AI Workflow 定义的四次演进:从 Markdown 到 JS 脚本,再到分布式多 Agent
javascript·人工智能·agent
一颗烂土豆9 小时前
Meshopt 压缩深度解析,为什么它比 Draco 更快
前端·javascript·webgl
kyriewen12 小时前
同事每天催我 Code Review,我写了个脚本让 AI 替我 review PR——现在他反过来催 AI 了
前端·javascript·ai编程
weedsfly14 小时前
迭代器、生成器与异步迭代——让数据“按需流动”的艺术
前端·javascript
假如让我当三天老蒯14 小时前
前端跨域解决方案(学习用)
前端·javascript·面试
铁皮饭盒16 小时前
Bun 哪比 Node.js 快?
javascript·后端
JieE2121 天前
LeetCode 56. 合并区间|超清晰 JS 图解思路,面试高频区间题
javascript·算法·面试
candyTong1 天前
RTK 技术原理:一次典型会话里,80% 上下文是怎么省下来的
javascript·后端·架构
_柳青杨1 天前
深入理解 JavaScript 事件循环
前端·javascript