力扣hot100之226:翻转二叉树
力扣hot100之226:翻转二叉树

LeetCode 226. 翻转二叉树

题目描述

给你一棵二叉树的根节点 root ,请你翻转这棵二叉树,并返回其根节点。

所谓“翻转二叉树”,就是把每个节点的左子树和右子树交换。

比如原来的树是:

        4
      /   \
     2     7
    / \   / \
   1   3 6   9

翻转之后会变成:

        4
      /   \
     7     2
    / \   / \
   9   6 3   1

也就是说,整棵树中的每一个节点,都要把左右孩子交换一下。


解题思路

这道题是一道非常经典的二叉树题目。

题目本身不复杂,它真正的核心其实只有一句话:

遍历整棵树,把每个节点的左孩子和右孩子交换。

所以这道题无论你是用:

  • 递归

  • 迭代

本质都一样,只是“遍历树”的方式不同而已。

你给出的正好是这道题最常见的两种写法:

  1. 递归写法

  2. 栈模拟 DFS 的迭代写法

下面分别来讲。


方法一:递归解法

代码

class Solution {
    public TreeNode invertTree(TreeNode root) {
        if (root == null) return null;
        TreeNode tmp = root.left;
        root.left = invertTree(root.right);
        root.right = invertTree(tmp);
        return root;
    }
}

递归思路

递归写法非常符合这道题的定义。

对于任意一个节点 root

  1. 先翻转它的右子树

  2. 再翻转它原来的左子树

  3. 最后把它们交换过来

也可以理解成:

  • 当前节点要翻转

  • 那它的左右子树也都必须先各自翻转好

  • 然后再挂回相反的位置

这正是递归最适合处理的结构。


为什么递归能做这题

因为二叉树天然具有“子问题结构”:

一棵树的翻转,可以拆成“左子树翻转 + 右子树翻转 + 当前节点交换”。

也就是说:

  • 大问题:翻转整棵树

  • 小问题:翻转左子树、翻转右子树

而左子树和右子树本身又是二叉树,所以可以继续递归处理。


代码解析

1. 递归终止条件

if (root == null) return null;

如果当前节点为空,说明已经递归到叶子节点下面了,直接返回 null

这是二叉树递归最基本的边界条件。


2. 先保存原来的左子树

TreeNode tmp = root.left;

因为后面要修改 root.left,所以必须先把原来的左子树保存下来。

不然一旦先改掉,就找不到原来的左子树了。


3. 左子树改成“翻转后的右子树”

root.left = invertTree(root.right);

这里先递归翻转右子树,然后把结果挂到左边。


4. 右子树改成“翻转后的原左子树”

root.right = invertTree(tmp);

因为原来的左子树已经保存在 tmp 里,所以这里递归翻转它,再挂到右边。


5. 返回当前节点

return root;

表示以当前节点为根的整棵子树已经翻转完成。


递归执行过程示例

我们以这棵树为例:

        4
      /   \
     2     7
    / \   / \
   1   3 6   9

处理根节点 4 时:

  • 保存左子树 2

  • 递归翻转右子树 7

  • 再递归翻转左子树 2

  • 最后交换位置

先翻转节点 7

    7
   / \
  6   9

翻转后:

    7
   / \
  9   6

再翻转节点 2

    2
   / \
  1   3

翻转后:

    2
   / \
  3   1

最后挂回根节点 4

得到:

        4
      /   \
     7     2
    / \   / \
   9   6 3   1

递归写法的特点

优点:

  • 代码非常简洁

  • 特别符合树的定义

  • 很容易写出来

缺点:

  • 依赖系统递归栈

  • 如果树特别深,可能会有栈溢出的风险


方法二:迭代解法

代码

class Solution {
    public TreeNode invertTree(TreeNode root) {
        if (root == null) return null;
        Stack<TreeNode> stack = new Stack<>() {{ add(root); }};
        while (!stack.isEmpty()) {
            TreeNode node = stack.pop();
            if (node.left != null) stack.add(node.left);
            if (node.right != null) stack.add(node.right);
            TreeNode tmp = node.left;
            node.left = node.right;
            node.right = tmp;
        }
        return root;
    }
}

迭代思路

迭代写法本质上也是在遍历整棵树,只不过它不依赖递归,而是自己手动用一个栈来模拟深度优先搜索(DFS)。

整体流程很简单:

  1. 先把根节点压栈

  2. 每次弹出一个节点

  3. 交换这个节点的左右孩子

  4. 再把它的左右孩子压栈,继续处理

  5. 直到栈为空

这样整棵树的每个节点都会被访问一次,每个节点的左右子树都会被交换一次。


代码解析

1. 判空

if (root == null) return null;

如果树为空,直接返回。


2. 初始化栈

Stack<TreeNode> stack = new Stack<>() {{ add(root); }};

把根节点先放进栈里,作为 DFS 的起点。

这里用的是匿名内部类初始化写法,本质上就是把 root 先压进去。

如果写得更普通一点,也可以写成:

Stack<TreeNode> stack = new Stack<>();
stack.push(root);

效果一样。


3. 不断弹出节点

while (!stack.isEmpty()) {
    TreeNode node = stack.pop();

每次从栈中取出一个节点进行处理。


4. 把左右孩子压栈

if (node.left != null) stack.add(node.left);
if (node.right != null) stack.add(node.right);

把当前节点的左右孩子加入栈中,后面继续处理它们。

这里注意一点:

是先把孩子压栈,再交换当前节点的左右孩子。

这么写完全没问题,因为无论先压栈还是先交换,只要压进去的是原来的两个子节点对象,后面都能继续遍历到。


5. 交换左右子树

TreeNode tmp = node.left;
node.left = node.right;
node.right = tmp;

这三句就是翻转当前节点的核心操作。


6. 返回根节点

return root;

整棵树处理完成后,返回根节点。


迭代执行过程示例

还是以这棵树为例:

        4
      /   \
     2     7
    / \   / \
   1   3 6   9

初始:

stack = [4]

第一次弹出 4

交换 4 的左右孩子:

        4
      /   \
     7     2

然后把原来的左右孩子压栈。


第二次弹出 7

交换 7 的左右孩子:

    7
   / \
  9   6

第三次弹出 2

交换 2 的左右孩子:

    2
   / \
  3   1

继续处理剩下的叶子节点时,因为它们左右孩子都为空,所以交换后没有变化。

最终整棵树就翻转完成了。


两种方法的本质

无论是递归还是迭代,这道题都在做同一件事:

访问每一个节点,并交换它的左孩子和右孩子。

区别只在于遍历方式不同:

  • 递归:依赖函数调用栈

  • 迭代:手动维护一个显式栈

所以你可以把这题理解成一道“树的遍历题”,而不是复杂的构造题。


两种方法对比

递归写法

优点:

  • 写法最简洁

  • 最符合树的递归定义

  • 面试中很常见

缺点:

  • 依赖系统栈

  • 树很深时可能爆栈


迭代写法

优点:

  • 不依赖递归

  • 更适合特别深的树

缺点:

  • 代码稍长一些

  • 需要自己维护栈


复杂度分析

设二叉树有 n 个节点。

时间复杂度

两种方法都是:

O(n)

因为每个节点都只会被访问一次。


空间复杂度

递归

O(h)

其中 h 是树的高度。 最坏情况下,如果树退化成链表,空间复杂度是 O(n)

迭代

O(n)

最坏情况下,栈中可能会存放较多节点。

如果从树高角度分析,平均也和树结构有关。


为什么这题这么经典

这道题本身并不难,但它几乎成了二叉树题的“入门代表题”。

因为它能很好地练习这些基础能力:

  1. 是否理解二叉树递归

  2. 是否会写树的 DFS

  3. 是否能正确交换指针

  4. 是否知道递归和迭代的对应关系

所以虽然题目简单,但非常适合拿来打牢树的基本功。


总结

这道题的核心其实就一句话:

遍历整棵树,把每个节点的左右孩子交换。

对应到实现上:

  • 递归写法:先递归翻转左右子树,再交换位置

  • 迭代写法:用栈遍历整棵树,访问到一个节点就交换一次

所以你可以把这题记成一类非常典型的树题:

树的每个节点做相同操作,然后递归 / 迭代处理整棵树。

这类思想在后面的很多二叉树题里都会反复出现。


如果对您有帮助的话,能否支持一下博主?
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇