【LeetCode Hot100 刷题日记 (38/100)】226. 翻转二叉树 —— 树、深度优先搜索(DFS)、递归🌳

2 阅读5分钟

📌 题目链接:226. 翻转二叉树 - 力扣(LeetCode)

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

⏱️ 时间复杂度:O(N)

💾 空间复杂度:O(H),H 为树的高度(最坏 O(N),平均 O(log N))


🧠 题目分析

本题要求我们翻转一棵二叉树,即对于每个节点,将其左右子树交换位置。这听起来简单,但背后蕴含了经典的树遍历 + 递归结构思想

💡 面试高频点

  • “翻转二叉树” 是 Google 曾经的面试题(Max Howell 的梗:“Google: 90% of our engineers use the software you wrote (Homebrew), but you can’t invert a binary tree on a whiteboard so f*** off.”)
  • 考察你对递归终止条件、子问题分解、树结构操作的理解。

输入是一棵二叉树的根节点 root,输出是翻转后的根节点。注意:

  • 空树直接返回 nullptr
  • 翻转是原地操作(虽然题目没强制,但通常期望如此)
  • 不需要新建节点,只需调整指针方向

🔁 核心算法及代码讲解:递归 + 深度优先搜索(DFS)

✅ 核心思想

采用后序遍历(Post-order Traversal) 的递归策略:

  1. 先递归翻转左子树
  2. 再递归翻转右子树
  3. 最后将当前节点的左右子树指针交换

📌 为什么是后序?
因为我们希望先处理子树,再处理当前节点。只有当左右子树已经翻转完毕,我们才能安全地交换它们。这正是后序遍历的典型应用场景。

🧩 递归三要素

要素说明
递归函数定义invertTree(root) 返回以 root 为根的翻转后二叉树的根
终止条件root == nullptr,空节点无需翻转,直接返回
递归关系翻转左子树 → 翻转右子树 → 交换左右指针

💻 C++ 核心代码(带详细行注释)

TreeNode* invertHere(TreeNode* root) {
    // 终止条件:空节点,直接返回
    if (root == nullptr) {
        return nullptr;
    }
    // 递归翻转左子树,得到翻转后的左子树根
    TreeNode* left = invertHere(root->left);
    // 递归翻转右子树,得到翻转后的右子树根
    TreeNode* right = invertHere(root->right);
    // 交换:原左子树变成右子树,原右子树变成左子树
    root->left = right;   // 注意:这里赋值的是已经翻转好的子树
    root->right = left;
    // 返回当前已翻转的子树根
    return root;
}

⚠️ 注意:有些初学者会写成先交换再递归,那是前序遍历,也能正确翻转!但逻辑上不如后序清晰。两种方式都可行,但后序更符合“子问题先解决”的直觉


🧭 解题思路(分步拆解)

  1. 理解“翻转”的含义
    对于任意节点,其左孩子变为右孩子,右孩子变为左孩子。整棵树镜像对称。

  2. 选择遍历方式

    • 前序(根→左→右):先交换当前节点的左右子树,再递归处理子树

    • 后序(左→右→根):先递归处理子树,再交换当前节点的左右子树

      ✅ 两者均可!但后序更自然,因为我们在“回溯”时完成翻转。

  3. 处理边界情况

    • 输入为空树(root == nullptr)→ 直接返回 nullptr
    • 只有根节点 → 交换左右(均为 null),结果不变
  4. 实现递归函数
    按照上述三要素编写,确保每层递归只做一次交换,且不遗漏任何节点。

  5. 验证示例
    示例1:[4,2,7,1,3,6,9]

    • 翻转后应为 [4,7,2,9,6,3,1]
    • 手动画图可验证:4 的左右子(2,7)交换 → 2 的子(1,3)交换 → 7 的子(6,9)交换

📊 算法分析

项目分析
时间复杂度O(N) —— 每个节点访问一次
空间复杂度O(H) —— 递归栈深度等于树高 H • 平均情况(平衡树):O(log N) • 最坏情况(链状树):O(N)
是否原地操作✅ 是,仅修改指针,未创建新节点
可迭代实现吗?✅ 可用栈模拟 DFS(前序或后序),但递归更简洁

💬 面试加分项

  • 主动提到“也可以用 BFS(层序遍历)+ 队列实现”
  • 比较递归 vs 迭代的优劣:递归代码简洁但有栈溢出风险;迭代更安全但代码稍长
  • 强调“翻转是镜像操作,不是反转值”

💻 完整代码

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:
    TreeNode* invertTree(TreeNode* root) {
        if (root == nullptr) {
            return nullptr;
        }
        TreeNode* left = invertTree(root->left);
        TreeNode* right = invertTree(root->right);
        root->left = right;
        root->right = left;
        return root;
    }
};

// 测试
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);

    // 构建示例1: [4,2,7,1,3,6,9]
    TreeNode* root = new TreeNode(4);
    root->left = new TreeNode(2);
    root->right = new TreeNode(7);
    root->left->left = new TreeNode(1);
    root->left->right = new TreeNode(3);
    root->right->left = new TreeNode(6);
    root->right->right = new TreeNode(9);

    Solution sol;
    TreeNode* inverted = sol.invertTree(root);

    // 简单验证:中序遍历应为 9,7,6,4,3,2,1(非标准,仅示意)
    // 实际建议用层序打印验证,此处省略

    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 {TreeNode}
 */
var invertTree = function(root) {
    if (root === null) {
        return null;
    }
    const left = invertTree(root.left);
    const right = invertTree(root.right);
    root.left = right;
    root.right = left;
    return root;
};

// 测试用例
// 构建树 [4,2,7,1,3,6,9]
const root = {
    val: 4,
    left: {
        val: 2,
        left: { val: 1, left: null, right: null },
        right: { val: 3, left: null, right: null }
    },
    right: {
        val: 7,
        left: { val: 6, left: null, right: null },
        right: { val: 9, left: null, right: null }
    }
};

console.log(invertTree(root));

🌟 本期完结,下期见!🔥

👉 点赞收藏加关注,新文更新不迷路。关注专栏【算法】LeetCode Hot100刷题日记,持续为你拆解每一道热题的底层逻辑与面试技巧!

💬 欢迎留言交流你的解法或疑问!一起进步,冲向 Offer!💪

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