【力扣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)
核心思想 同步遍历对比外侧内侧 层序遍历配对对比
相关推荐
Keven_1119 分钟前
算法札记:ACM中将高精度算法融于题目的模板
算法·acm·精度
小七在进步21 分钟前
数据结构:快速排序
数据结构·算法·排序算法
闻缺陷则喜何志丹24 分钟前
【计算几何 第十一章】凸包:混合物
数学·算法·计算几何·凸包·混合物·凸组合
土司大王25 分钟前
LeetCode hot100——二叉树的最大深度
算法·leetcode·职场和发展
高亦真33 分钟前
今天是学习嵌入式的第31天
linux·学习·算法
圣保罗的大教堂33 分钟前
leetcode 3720. 大于目标字符串的最小字典序排列 中等
leetcode
让学习成为一种生活方式43 分钟前
黄花蒿LHC基因家族的串联重复驱动扩张及其UV-B胁迫适应性--BMC Plant Biology
人工智能·算法·机器学习
ECT-OS-JiuHuaShan1 小时前
共轭互逆链路论,彻底打击庸俗辩证法和不可知论
数据库·人工智能·算法·机器学习·数学建模
薛定e的猫咪1 小时前
【大模型量化】使用 llama.cpp 完成量化、本地推理与服务化部署
人工智能·深度学习·算法·llama
薛定e的猫咪1 小时前
【源码解读版】ContraBAR:用 CPC 对比学习替代变分推断做贝叶斯元 RL
人工智能·深度学习·学习·算法·机器学习