📌 题目链接:101. 对称二叉树 - 力扣(LeetCode)
🔍 难度:简单 | 🏷️ 标签:树、深度优先搜索(DFS)、广度优先搜索(BFS)、递归、迭代
⏱️ 目标时间复杂度:O(n)
💾 空间复杂度:O(n)(递归栈或队列)
题目分析
给定一棵二叉树的根节点 root,判断该树是否轴对称(即以根节点为对称轴,左右子树互为镜像)。
✅ 关键理解:
“对称” ≠ “结构相同”,而是左子树的左孩子 = 右子树的右孩子,且左子树的右孩子 = 右子树的左孩子。
这本质上是一个双指针同步遍历 + 镜像比较的问题。
核心算法及代码讲解
本题有两种主流解法:递归(DFS) 和 迭代(BFS)。两者都基于“镜像对称”的定义展开。
✅ 方法一:递归(深度优先搜索)
🧠 核心思想:
- 定义一个辅助函数
check(p, q),用于判断两棵树p和q是否互为镜像。 - 递归条件:
- 若
p和q都为空 → 对称 ✅ - 若其中一个为空 → 不对称 ❌
- 若值不等 → 不对称 ❌
- 否则,继续递归检查:
p.left与q.right是否对称,且p.right与q.left是否对称。
- 若
💡 面试重点:
- 递归终止条件必须覆盖所有边界(空指针处理)
- 参数设计体现“镜像”思想:左 vs 右,右 vs 左
🔍 代码详解(C++)
bool check(TreeNode *p, TreeNode *q) {
if (!p && !q) return true; // 两者都空,对称
if (!p || !q) return false; // 仅一个空,不对称
return p->val == q->val // 值相等
&& check(p->left, q->right) // 左子树 vs 右子树(镜像)
&& check(p->right, q->left); // 右子树 vs 左子树(镜像)
}
调用入口:
bool isSymmetric(TreeNode* root) {
if (!root) return true; // 空树视为对称
return check(root->left, root->right); // 比较左右子树是否镜像
}
✅ 方法二:迭代(广度优先搜索 + 队列模拟)
🧠 核心思想:
- 使用队列模拟递归过程。
- 初始将
root入队两次(代表两个“虚拟镜像根”)。 - 每次从队列中取出两个节点
u和v,进行如下操作:- 若都为空 → 继续
- 若一个空或值不等 → 返回
false - 否则,按镜像顺序入队:
u.left与v.right一组,u.right与v.left一组
💡 面试亮点:
- 迭代法避免了递归栈溢出风险(虽然本题 n ≤ 1000,但大厂常问“如何不用递归?”)
- 队列中始终成对处理节点,体现“同步镜像遍历”思想
🔍 代码详解(C++)
bool check(TreeNode *u, TreeNode *v) {
queue<TreeNode*> q;
q.push(u);
q.push(v);
while (!q.empty()) {
u = q.front(); q.pop();
v = q.front(); q.pop();
if (!u && !v) continue; // 都空,跳过
if (!u || !v || u->val != v->val) // 一个空 或 值不同
return false;
// 按镜像顺序入队:左-右,右-左
q.push(u->left);
q.push(v->right);
q.push(u->right);
q.push(v->left);
}
return true;
}
调用入口:
bool isSymmetric(TreeNode* root) {
return check(root, root); // 传入两次 root,启动镜像比较
}
⚠️ 注意:这里传
root, root而非root->left, root->right,是为了统一处理空树情况(若 root 为空,直接进入 while 循环判断)。
解题思路(分步拆解)
📌 递归法步骤:
- 边界处理:若根为空,直接返回
true。 - 定义镜像判断函数
check(p, q)。 - 递归比较:
- 比较当前节点值
- 递归比较
p.left与q.right - 递归比较
p.right与q.left
- 返回结果。
📌 迭代法步骤:
- 创建队列,初始压入
root两次。 - 循环直到队列为空:
- 弹出两个节点
u,v - 若都空 → 继续
- 若不满足对称条件 → 返回
false - 按镜像顺序压入子节点
- 弹出两个节点
- 若循环结束未返回
false→ 返回true。
算法分析
| 方法 | 时间复杂度 | 空间复杂度 | 适用场景 |
|---|---|---|---|
| 递归 | O(n) | O(n)(最坏退化为链表) | 代码简洁,逻辑清晰 |
| 迭代 | O(n) | O(n)(队列存储) | 避免栈溢出,适合深度大的树 |
🎯 面试高频追问:
- Q:为什么递归空间是 O(n)?
A:最坏情况下(如单边倾斜树),递归深度为 n,系统栈占用 O(n)。- Q:能否优化空间?
A:不能。因为必须访问所有节点才能判断对称性,信息下限为 O(n)。- Q:如果树很大(百万节点),哪种方法更安全?
A:迭代法,避免栈溢出(Stack Overflow)。
代码
✅ C++ 完整代码(保留原模板)
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
// Definition for a binary tree node.
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode() : val(0), left(nullptr), right(nullptr) {}
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
};
class Solution {
public:
bool check(TreeNode *p, TreeNode *q) {
if (!p && !q) return true;
if (!p || !q) return false;
return p->val == q->val && check(p->left, q->right) && check(p->right, q->left);
}
bool isSymmetric(TreeNode* root) {
return check(root->left, root->right);
}
};
// 测试
signed main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
// 测试用例 1: [1,2,2,3,4,4,3] → true
TreeNode *root1 = new TreeNode(1);
root1->left = new TreeNode(2);
root1->right = new TreeNode(2);
root1->left->left = new TreeNode(3);
root1->left->right = new TreeNode(4);
root1->right->left = new TreeNode(4);
root1->right->right = new TreeNode(3);
cout << "Test 1: " << (Solution().isSymmetric(root1) ? "true" : "false") << "\n"; // true
// 测试用例 2: [1,2,2,null,3,null,3] → false
TreeNode *root2 = new TreeNode(1);
root2->left = new TreeNode(2);
root2->right = new TreeNode(2);
root2->left->right = new TreeNode(3);
root2->right->right = new TreeNode(3);
cout << "Test 2: " << (Solution().isSymmetric(root2) ? "true" : "false") << "\n"; // false
// 测试用例 3: [] → true(但题目保证至少1个节点,此例可选)
// TreeNode *root3 = nullptr;
// cout << "Test 3: " << (Solution().isSymmetric(root3) ? "true" : "false") << "\n";
return 0;
}
✅ JavaScript 完整代码
/**
* 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) {
function check(p, q) {
if (!p && !q) return true;
if (!p || !q) return false;
return p.val === q.val && check(p.left, q.right) && check(p.right, q.left);
}
return check(root.left, root.right);
};
// 测试用例
function TreeNode(val, left, right) {
this.val = (val === undefined ? 0 : val);
this.left = (left === undefined ? null : left);
this.right = (right === undefined ? null : right);
}
// Test 1
let root1 = new TreeNode(1);
root1.left = new TreeNode(2);
root1.right = new TreeNode(2);
root1.left.left = new TreeNode(3);
root1.left.right = new TreeNode(4);
root1.right.left = new TreeNode(4);
root1.right.right = new TreeNode(3);
console.log("Test 1:", isSymmetric(root1)); // true
// Test 2
let root2 = new TreeNode(1);
root2.left = new TreeNode(2);
root2.right = new TreeNode(2);
root2.left.right = new TreeNode(3);
root2.right.right = new TreeNode(3);
console.log("Test 2:", isSymmetric(root2)); // false
🌟 本期完结,下期见!🔥
👉 点赞收藏加关注,新文更新不迷路。关注专栏【算法】LeetCode Hot100刷题日记,持续为你拆解每一道热题的底层逻辑与面试技巧!
💬 欢迎留言交流你的解法或疑问!一起进步,冲向 Offer!💪
📌 记住:当你在刷题时,不要只看答案,要像写这篇文章一样,深入思考每一步背后的原理、优化空间和面试价值。这才是真正提升算法能力的方式!