目录
- [LeetCode 110. 平衡二叉树](#LeetCode 110. 平衡二叉树)
- 一、先确定思路
- 二、第一反应:使用最大深度的递归
- [三、第一次尝试:直接返回 False?](#三、第一次尝试:直接返回 False?)
- [四、重新设计返回值:使用 -1 表示不平衡](#四、重新设计返回值:使用 -1 表示不平衡)
- 五、有一个递归陷阱
- [六、正确思路:让 -1 向上传递](#六、正确思路:让 -1 向上传递)
- 七、完整代码
- 八、复杂度分析
- 九、最后复盘
LeetCode 110. 平衡二叉树
题目链接:110. 平衡二叉树
如果还没有看过二叉树递归的基础,建议先看这个题解,里面有讲解:LeetCode 104. 二叉树的最大深度(递归解法)
这两道题的递归思路非常接近。
104 的核心:
求每个节点左右子树的高度,然后得到当前节点高度。
而 110:
在求高度的过程中,同时判断高度差是否超过 1。
所以如果已经理解了 104,这道题实际上就是在原来的递归过程中增加一个判断。
另外,弄了个网站,可以看到代码执行过程中树的变化: 过程可视化
一、先确定思路
题目要求是什么?
给定一棵二叉树,判断它是否是高度平衡二叉树。
平衡二叉树的定义为:对于每一个节点,左子树高度 - 右子树高度的绝对值不能超过 1
也就是说,对于每个节点,都需要满足:
python
abs(left_height - right_height) <= 1
二、第一反应:使用最大深度的递归
看到这道题,我首先想到的是上一题 LeetCode 104. 二叉树的最大深度的递归解法。
因为判断平衡需要知道:
左右子树高度
而高度正是上一题求解的内容。
所以很自然地就想到:
对于当前节点:
- 求左子树高度
- 求右子树高度
- 判断高度差
- 返回当前节点高度
流程:
当前节点
/ \
左子树高度 右子树高度
↓
判断高度差是否 <= 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,表示当前子树不平衡
也就是说,递归函数不仅可以返回计算结果,也可以通过特殊值传递状态。
以后遇到类似问题时,可以考虑:
正常结果 + 特殊失败标记
让子问题的状态向上传递。