DAY27: LeetCode 110. 平衡二叉树 | 在递归中判断是否平衡

目录

LeetCode 110. 平衡二叉树

题目链接:110. 平衡二叉树

如果还没有看过二叉树递归的基础,建议先看这个题解,里面有讲解:LeetCode 104. 二叉树的最大深度(递归解法)

这两道题的递归思路非常接近。

104 的核心:

求每个节点左右子树的高度,然后得到当前节点高度。

而 110:

在求高度的过程中,同时判断高度差是否超过 1。

所以如果已经理解了 104,这道题实际上就是在原来的递归过程中增加一个判断。

另外,弄了个网站,可以看到代码执行过程中树的变化: 过程可视化


一、先确定思路

题目要求是什么?

给定一棵二叉树,判断它是否是高度平衡二叉树。

平衡二叉树的定义为:对于每一个节点,左子树高度 - 右子树高度的绝对值不能超过 1

也就是说,对于每个节点,都需要满足:

python 复制代码
abs(left_height - right_height) <= 1

二、第一反应:使用最大深度的递归

看到这道题,我首先想到的是上一题 LeetCode 104. 二叉树的最大深度的递归解法。

因为判断平衡需要知道:

复制代码
左右子树高度

而高度正是上一题求解的内容。

所以很自然地就想到:

对于当前节点:

  1. 求左子树高度
  2. 求右子树高度
  3. 判断高度差
  4. 返回当前节点高度

流程:

复制代码
             当前节点
              /    \
       左子树高度  右子树高度
             ↓
     判断高度差是否 <= 1
             ↓
     返回当前节点高度

代码结构大概就是:

python 复制代码
left_depth = dfs(node.left)

right_depth = dfs(node.right)

判断左右高度差

return 当前高度

看起来没有问题,是吧?


三、第一次尝试:直接返回 False?

最开始我的想法其实很直接:

既然题目要求判断是否平衡,

那么如果发现:

python 复制代码
abs(right_depth - left_depth) > 1

说明当前节点不平衡。

那我直接返回:

python 复制代码
False

表示:

复制代码
当前子树不平衡

代码大概是:

python 复制代码
def find_depth(node):
    if node is None:
        return 0

    left_depth = find_depth(node.left)
    right_depth = find_depth(node.right)

    if abs(right_depth - left_depth) > 1:
        return False

    return 1 + max(left_depth, right_depth)


find_depth(root)

return True

但是提交之后发现:

无论什么情况,最后返回的都是:

python 复制代码
True

🚩 为什么?

问题出在:

python 复制代码
find_depth(root)

这里。

虽然递归过程中可能返回:

python 复制代码
False

但是我只是调用了这个函数。

并没有接收它的返回值。

也就是说:

即使下面某个节点发现:

复制代码
不平衡

返回:

python 复制代码
False

最后执行的依然是:

python 复制代码
return True

所以结果永远都是 True。


那么修改成:

python 复制代码
return find_depth(root)

是不是就可以了?

但是又出现了新的问题。

因为这个递归函数本来负责返回:

复制代码
当前节点高度

正常情况下:

python 复制代码
return 1 + max(left_depth, right_depth)

返回的是:

复制代码
0、1、2、3...

但是现在:

有时候返回:

python 复制代码
False

有时候返回:

python 复制代码
高度

也就是说:同一个返回值出现了两种含义。

父节点需要的是:

python 复制代码
数字高度

但是收到:

python 复制代码
False

之后,就无法正常计算。

所以,False 不适合作为这个递归函数的返回值。


四、重新设计返回值:使用 -1 表示不平衡

那么,我们既需要返回:

复制代码
当前节点高度

又需要告诉上层:

复制代码
当前子树是否不平衡

怎么办?

我们需要一个特殊标记。

正常高度:

复制代码
0、1、2、3...

那么可以选择:

复制代码
-1

因为高度不可能为负数。

所以,返回值有两种含义:

正常:

复制代码
>=0 表示当前节点高度

异常:

复制代码
-1 表示当前子树不平衡

这样递归函数就可以同时完成:

复制代码
计算高度
+
判断是否平衡

但是,这里还有一个问题。


五、有一个递归陷阱

修改后,我写出了这样的代码:

python 复制代码
def find_depth(node):
    if node is None:
        return 0

    left_depth = find_depth(node.left)
    right_depth = find_depth(node.right)

    if abs(right_depth - left_depth) > 1:
        return -1

    return 1 + max(left_depth, right_depth)

给的三个例子都过了,但是提交的时候有个例子报错了。

那,是哪里出错了?

在这里,我们已经规定了

复制代码
返回 -1

代表 当前子树不平衡

但是问题在于:

虽然我们约定了 -1 的含义,

但是后面的节点仍然把它当成普通高度参与计算。

🔖 例如:

某个节点发现:

复制代码
左子树不平衡

于是:

python 复制代码
left_depth = -1

但是父节点继续执行:

python 复制代码
return 1 + max(left_depth, right_depth)

此时:

python 复制代码
max(-1, right_depth)

会把:

复制代码
-1

当成普通高度处理。

也就是说:

本来 不平衡 这个状态,被重新转换成了:

复制代码
正常高度

最终导致错误。


六、正确思路:让 -1 向上传递

ok,既然:

复制代码
-1

表示:

复制代码
下面已经不平衡

那么一旦发现 子树返回 -1 → 当前节点也应该直接返回 -1。

也就是:

python 复制代码
left_depth = find_depth(node.left)

if left_depth == -1:
    return -1

右子树同理:

python 复制代码
right_depth = find_depth(node.right)

if right_depth == -1:
    return -1

这样:

复制代码
叶子节点
↓
发现不平衡
↓
返回 -1
↓
父节点收到 -1
↓
继续返回 -1
↓
根节点知道整棵树不平衡

这个过程类似:错误状态一路向上传播。


七、完整代码

python 复制代码
class Solution:
    def isBalanced(self, root: TreeNode | None) -> bool:

        def find_depth(node):

            if node is None:
                return 0

            left_depth = find_depth(node.left)

            # 左子树已经不平衡
            if left_depth == -1:
                return -1

            right_depth = find_depth(node.right)

            # 右子树已经不平衡
            if right_depth == -1:
                return -1

            # 当前节点左右高度差超过1
            if abs(right_depth - left_depth) > 1:
                return -1

            # 返回当前节点高度
            return 1 + max(left_depth, right_depth)

        return find_depth(root) != -1

为什么不能最后再判断?

一种容易想到的做法是:

复制代码
先计算所有节点高度
最后判断是否平衡

但是这样需要额外遍历整棵树。

而我们现在的写法是在计算高度的过程中,同时判断是否平衡。

如果某个节点已经发现不满足条件:

复制代码
高度差 > 1

那么说明当前子树已经不平衡,没有必要继续向上计算高度。

例如:

复制代码
         1
       /   \
      2     3
     /
    4
   /
  5

节点 2 在计算左右子树高度时,就已经发现高度差超过 1。

因此直接返回:

复制代码
-1

让这个状态向上传递即可。


八、复杂度分析

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

对于每个节点,我们只需要:

  • 递归计算左右子树高度;
  • 判断当前节点是否平衡;
  • 返回结果。

因此时间复杂度为:

复制代码
O(n)

其中 n 为二叉树节点数量。

递归过程中,需要保存从根节点到当前节点的调用路径。

因此空间复杂度为:

复制代码
O(h)

其中 h 为二叉树高度。


九、最后复盘

在 LeetCode 104. 二叉树的最大深度中, 返回值只有一个含义:

复制代码
当前节点高度

而在这道题中, 返回值同时承担两个作用:

复制代码
正常:返回当前节点高度
异常:返回 -1,表示当前子树不平衡

也就是说,递归函数不仅可以返回计算结果,也可以通过特殊值传递状态。

以后遇到类似问题时,可以考虑:

复制代码
正常结果 + 特殊失败标记

让子问题的状态向上传递。

相关推荐
Sammyyyyy1 小时前
jev-ultrafast 实战:浏览器操作代理加本地 AI 网关,把云控制台配置做成自动化流水线
运维·人工智能·自动化·ai编程·servbay
狂师1 小时前
爆火!新一代e2e AI测试框架,单日狂揽1500 star
前端·人工智能·测试
米小虾1 小时前
随机基线都打不过:为什么一整代视觉 Token 压缩方法在混合线性注意力上集体失效
人工智能
米小虾1 小时前
一条 rollout 的两种死法:RLVR 的学习信号漏在哪,怎么补
人工智能
yychen_java1 小时前
第十二篇:完整项目实战——构建可验证的能源智能运维 Agent 中枢
运维·人工智能·能源
loulanyue_1 小时前
蚂蚁集团AI Native研发实践:从面向人类到面向Agent的下一代技术架构演进——读平台技术事业群总架构师黄挺2026云栖专场演讲
人工智能·大模型·软件架构·蚂蚁集团
9624561 小时前
餐饮 SaaS 优惠券系统架构演进(七)营销定价引擎设计:活动、会员价、优惠券、买赠如何在同一 PriceQuote 中可解释地组合
java·运维·算法
HAHAXX81 小时前
影刀AI点数是怎么消耗的?拆解消耗规则 + 成本优化实战
人工智能·rpa
smartpi_ai1 小时前
离线语音识别模块选型指南:CI-03T、SU-32T、SU-03T 等模块在不同场景下的对比与选择
人工智能·ci/cd·语音识别