Problem: 110. 平衡二叉树
文章目录
- [1. 整体思路](#1. 整体思路)
- [2. 完整代码](#2. 完整代码)
- [3. 时空复杂度](#3. 时空复杂度)
-
-
- [时间复杂度: O ( N ) O(N) O(N)](#时间复杂度: O ( N ) O(N) O(N))
- [空间复杂度: O ( N ) O(N) O(N)](#空间复杂度: O ( N ) O(N) O(N))
-
1. 整体思路
核心问题
判断二叉树是否平衡。
算法逻辑
同样采用自底向上 的递归。
区别在于:
- 左子树优先检查 :
- 先递归计算左子树的高度
l。 - 关键剪枝 :如果左子树返回
-1(说明左边已经不平衡了),那么整棵树肯定不平衡。此时不需要再递归计算右子树 ,直接向上返回-1。
- 先递归计算左子树的高度
- 右子树检查 :
- 只有当左子树平衡(
l != -1)时,才去递归计算右子树的高度r。 - 如果右子树返回
-1,同样直接向上返回-1。
- 只有当左子树平衡(
- 高度差检查 :
- 如果左右子树都平衡,最后检查当前节点的左右高度差
Math.abs(l - r)。 - 如果超过 1,返回
-1。 - 否则返回当前高度
max(l, r) + 1。
- 如果左右子树都平衡,最后检查当前节点的左右高度差
这种写法在遇到不平衡树时(特别是左子树就不平衡的情况),能够显著减少递归调用的次数。
2. 完整代码
java
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public boolean isBalanced(TreeNode root) {
// 调用 dfs,如果返回 -1 说明树不平衡
return dfs(root) != -1;
}
private int dfs(TreeNode node) {
// 递归终止条件:空节点高度为 0
if (node == null) {
return 0;
}
// 1. 先递归左子树
int l = dfs(node.left);
// 剪枝优化:如果左子树已经不平衡,直接返回 -1
// 这一步避免了后续对右子树的递归调用 dfs(node.right)
if (l == -1) {
return -1;
}
// 2. 只有左子树平衡,才递归右子树
int r = dfs(node.right);
// 检查右子树是否不平衡,或者当前节点左右高度差是否 > 1
// 如果任意条件满足,标记为不平衡 (-1)
if (r == -1 || Math.abs(l - r) > 1) {
return -1;
}
// 3. 左右子树都平衡且高度差 <= 1,返回当前节点高度
return Math.max(l, r) + 1;
}
}
3. 时空复杂度
假设二叉树节点数为 N N N。
时间复杂度: O ( N ) O(N) O(N)
- 计算依据 :
- 最坏情况下(整棵树是平衡的,或者不平衡发生在最右下角),我们需要遍历所有节点,每个节点访问一次。
- 最好情况下(根节点的左子树直接就不平衡),我们只需要访问左子树的部分节点,甚至只访问常数个节点(如果树极度不平衡且偏左)。
- 总体上仍是线性复杂度 O ( N ) O(N) O(N),但在平均情况下比无剪枝版本更快。
- 结论 : O ( N ) O(N) O(N)。
空间复杂度: O ( N ) O(N) O(N)
- 计算依据 :
- 空间消耗主要来自递归栈。
- 最坏情况(退化为链表): O ( N ) O(N) O(N)。
- 平均/最好情况(平衡树): O ( log N ) O(\log N) O(logN)。
- 结论 : O ( N ) O(N) O(N)。