【LeetCode Hot100 刷题日记 (39/100)】101. 对称二叉树——深度优先搜索(DFS)、广度优先搜索(BFS)、递归迭代(镜像判断)🌳

10 阅读5分钟

📌 题目链接:101. 对称二叉树 - 力扣(LeetCode)

🔍 难度:简单 | 🏷️ 标签:树、深度优先搜索(DFS)、广度优先搜索(BFS)、递归、迭代

⏱️ 目标时间复杂度:O(n)

💾 空间复杂度:O(n)(递归栈或队列)


题目分析

给定一棵二叉树的根节点 root,判断该树是否轴对称(即以根节点为对称轴,左右子树互为镜像)。

关键理解
“对称” ≠ “结构相同”,而是左子树的左孩子 = 右子树的右孩子,且左子树的右孩子 = 右子树的左孩子
这本质上是一个双指针同步遍历 + 镜像比较的问题。


核心算法及代码讲解

本题有两种主流解法:递归(DFS)迭代(BFS)。两者都基于“镜像对称”的定义展开。

✅ 方法一:递归(深度优先搜索)

🧠 核心思想:

  • 定义一个辅助函数 check(p, q),用于判断两棵树 pq 是否互为镜像。
  • 递归条件:
    1. pq 都为空 → 对称 ✅
    2. 若其中一个为空 → 不对称 ❌
    3. 若值不等 → 不对称 ❌
    4. 否则,继续递归检查:
      p.leftq.right 是否对称,且 p.rightq.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 入队两次(代表两个“虚拟镜像根”)。
  • 每次从队列中取出两个节点 uv,进行如下操作:
    • 若都为空 → 继续
    • 若一个空或值不等 → 返回 false
    • 否则,按镜像顺序入队:
      u.leftv.right 一组,u.rightv.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 循环判断)。


解题思路(分步拆解)

📌 递归法步骤:

  1. 边界处理:若根为空,直接返回 true
  2. 定义镜像判断函数 check(p, q)
  3. 递归比较
    • 比较当前节点值
    • 递归比较 p.leftq.right
    • 递归比较 p.rightq.left
  4. 返回结果

📌 迭代法步骤:

  1. 创建队列,初始压入 root 两次。
  2. 循环直到队列为空:
    • 弹出两个节点 u, v
    • 若都空 → 继续
    • 若不满足对称条件 → 返回 false
    • 按镜像顺序压入子节点
  3. 若循环结束未返回 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!💪

📌 记住:当你在刷题时,不要只看答案,要像写这篇文章一样,深入思考每一步背后的原理、优化空间和面试价值。这才是真正提升算法能力的方式!