【力扣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)
核心思想 同步遍历对比外侧内侧 层序遍历配对对比
相关推荐
凯瑟琳.奥古斯特11 小时前
力扣1235:加权区间调度最优解
java·python·算法·leetcode·职场和发展
耶叶12 小时前
餐厅出入最少人数问题:贪心算法
算法·贪心算法
gihigo199812 小时前
基于小波框架与稀疏表示的SAR图像目标识别系统(MATLAB实现)
算法
吴可可12312 小时前
CAD2004自定义实体开发环境配置
c++·算法
装不满的克莱因瓶12 小时前
矩阵的主成分是什么?主成分分析(PCA)又能做什么?
人工智能·线性代数·算法·机器学习·ai·矩阵·pca
大菜菜小个子12 小时前
template<typename T>使用
java·开发语言·算法
Fanfanaas12 小时前
C++ 继承
java·开发语言·jvm·c++·学习·算法
lqqjuly12 小时前
模型合并与融合:理论、算法与可运行实现—从损失曲面几何到多模型融合
算法
memcpy012 小时前
LeetCode 2144. 打折购买糖果的最小开销【贪心】
算法·leetcode·职场和发展
Purple Coder13 小时前
STM32基础(1)
职场和发展