一、题目描述
给你一个二叉树的根节点 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) |
| 核心思想 |
同步遍历对比外侧内侧 |
层序遍历配对对比 |