【力扣100题】29. 对称二叉树

一、题目描述

给你一个二叉树的根节点 root,检查它是否轴对称。

轴对称:左右子树互为镜像,即左子树的左孩子 == 右子树的右孩子,左子树的右孩子 == 右子树的左孩子。

示例

示例 输入 输出
示例1 root = [1,2,2,3,4,4,3] true
示例2 root = [1,2,2,null,3,null,3] false

提示

  • 树中节点数目在范围 [1, 1000]
  • -100 <= Node.val <= 100

进阶

你可以运用递归和迭代两种方法解决这个问题吗?


二、解题思路总览

方法 核心思想 时间复杂度 空间复杂度
递归 同步遍历左右子树,外侧对内侧 O(n) O(h),h为树高
迭代 层序遍历,对比每层是否对称 O(n) O(n)

对称的核心

  • 外侧对内侧:left->left vs right->right
  • 内侧对外侧:left->right vs right->left
  • 两组同时满足才对称

三、完整代码

方法一:递归

cpp 复制代码
class Solution {
public:
    bool traversal(TreeNode* list1, TreeNode* list2) {
        // 1. 两节点都为空,对称
        if (list1 == NULL && list2 == NULL) return true;
        // 2. 只有一个为空,不对称
        else if (list1 == NULL || list2 == NULL) return false;
        // 3. 节点值不同,不对称
        else if (list1->val != list2->val) return false;

        // 4. 外侧对比:list1的左 vs list2的右
        bool outside = traversal(list1->left, list2->right);
        // 5. 内侧对比:list1的右 vs list2的左
        bool inside = traversal(list1->right, list2->left);

        // 6. 外侧和内侧都对称才对称
        return outside & inside;
    }

    bool isSymmetric(TreeNode* root) {
        if (!root) return true;                        // 空树
        return traversal(root->left, root->right);    // 对比左右子树
    }
};

方法二:迭代(层序遍历)

cpp 复制代码
class Solution {
public:
    bool isSymmetric(TreeNode* root) {
        if (!root) return true;           // 空树
        queue<TreeNode*> q;               // 层序遍历队列
        q.push(root->left);
        q.push(root->right);

        while (!q.empty()) {
            TreeNode* left = q.front();   // 左子树节点
            q.pop();
            TreeNode* right = q.front();  // 右子树节点
            q.pop();

            // 两节点都为空,继续
            if (!left && !right) continue;

            // 只有一个为空,不对称
            if (!left || !right) return false;

            // 值不同,不对称
            if (left->val != right->val) return false;

            // 入队:外侧对内侧
            q.push(left->left);   // 左子树的左
            q.push(right->right);// 右子树的右

            // 入队:内侧对外侧
            q.push(left->right);  // 左子树的右
            q.push(right->left);  // 右子树的左
        }

        return true;
    }
};

四、算法流程图(ASCII)

对称结构示意

复制代码
对称树示例:[1,2,2,3,4,4,3]

        1
       / \
      2   2      ← 根节点左右对称
     /\  /\
    3  44  3
     \  |  /
      镜像关系

外侧对比:2(左)->left(3)  vs  2(右)->right(3)
内侧对比:2(左)->right(4) vs  2(右)->left(4)

递归版流程

复制代码
isSymmetric(1)
        │
        ▼
traversal(2, 2)  ← 比较左右子树根节点
        │
        ├── 比较2.val == 2.val ✓
        │
        ├── outside = traversal(3, 3) ← 外侧
        │       ├── 比较3.val == 3.val ✓
        │       ├── outside = traversal(NULL, NULL) → true
        │       ├── inside = traversal(NULL, NULL) → true
        │       └── return true & true = true
        │
        └── inside = traversal(4, 4) ← 内侧
                ├── 比较4.val == 4.val ✓
                ├── outside = traversal(NULL, NULL) → true
                ├── inside = traversal(NULL, NULL) → true
                └── return true & true = true

最终:true & true = true

五、逐行解析

递归版逐行解析

cpp 复制代码
class Solution {
public:
    bool traversal(TreeNode* list1, TreeNode* list2) {
        // ─────────────────────────────────────────
        // 第1步:两节点都为空,对称
        // 递归终止条件:最简单的对称情况
        // ─────────────────────────────────────────
        if (list1 == NULL && list2 == NULL) return true;

        // ─────────────────────────────────────────
        // 第2步:只有一个为空,不对称
        // 一个有值一个为空,必定不对称
        // ─────────────────────────────────────────
        else if (list1 == NULL || list2 == NULL) return false;

        // ─────────────────────────────────────────
        // 第3步:节点值不同,不对称
        // ─────────────────────────────────────────
        else if (list1->val != list2->val) return false;

        // ─────────────────────────────────────────
        // 第4步:外侧对比
        // list1的左孩子 vs list2的右孩子
        // 对称树:左子树的左 == 右子树的右
        // ─────────────────────────────────────────
        bool outside = traversal(list1->left, list2->right);

        // ─────────────────────────────────────────
        // 第5步:内侧对比
        // list1的右孩子 vs list2的左孩子
        // 对称树:左子树的右 == 右子树的左
        // ─────────────────────────────────────────
        bool inside = traversal(list1->right, list2->left);

        // ─────────────────────────────────────────
        // 第6步:外侧和内侧同时满足才对称
        // 用 & 而不是 &&:确保两边都计算完再判断
        // ─────────────────────────────────────────
        return outside & inside;
    }

    bool isSymmetric(TreeNode* root) {
        if (!root) return true;  // 空树是对称的

        // 从左右子树开始对比
        return traversal(root->left, root->right);
    }
};

六、复杂度分析

时间复杂度

方法 分析 复杂度
递归 每个节点访问一次 O(n)
迭代 每个节点访问一次 O(n)

空间复杂度

方法 分析 复杂度
递归 函数调用栈,最大深度为树高h O(h)
迭代 队列存储同层节点 O(n)

七、面试追问 FAQ

问题 回答
为什么递归要用 & 而不是 && & 按位与确保两边表达式都执行完再返回;&& 可能有短路效应
递归的终止条件有哪些? 两节点都空(对称)、只有一个空(不对称)、值不同(不对称)
迭代怎么保证对称? 层序遍历时,每次取出两个节点对比,外侧入队和内侧入队要配对
和翻转二叉树有什么区别? 对称是左vs右,翻转是交换左右孩子,思路类似但用途不同
如何记忆这个算法? 记住"外侧对内侧"的口诀:left.left vs right.right,left.right vs right.left

八、相关题目

题目 难度 关键点
101. 对称二叉树 简单 本题
100. 相同的树 简单 递归比较
226. 翻转二叉树 简单 交换子树
104. 二叉树的最大深度 简单 递归遍历

九、总结

对比项 递归 迭代
代码量 较多
时间复杂度 O(n) O(n)
空间复杂度 O(h) O(n)
核心思想 同步遍历对比外侧内侧 层序遍历配对对比
相关推荐
To_OC14 小时前
LC 1 两数之和:面试第一道必考题,暴力解法直接被面试官 pass
javascript·算法·leetcode
鱼鱼不愚与19 小时前
《原来如此 | 第01期:为什么导航软件能预测红绿灯倒计时?》
算法
复杂网络1 天前
论最小 Agent 计算机的形态
算法
kisshyshy2 天前
🍦 雪糕、食堂、火车厢:三幅漫画吃透栈、队列与链表
javascript·算法
猿人谷2 天前
不只是 CPU 阈值:STAR 如何用 GAT + Transformer 做容器级自动扩缩容?
人工智能·算法
复杂网络2 天前
Stable Diffusion 视觉大模型微调技术深度调研
算法
复杂网络2 天前
基于 Stable Diffusion 架构的视觉大模型代表性工作与原理深度解析
算法
MrZhao4002 天前
Agent Loop 如何用 Hook 扩展:权限、日志与工具拦截
算法
MrZhao4002 天前
Agent 为什么需要 Skills:别把所有知识都塞进 system prompt
算法