当前位置: 首页 > news >正文

leetcode110:判断平衡二叉树(两种思路对比)

文章目录

  • 一、 题目描述
  • 二、 核心思路分析
    • 1. 方法一:自顶向下的简单递归
      • 1.1 代码实现 (方法一,非最优解)
      • 1.2 效率分析
    • 2. 方法二:自底向上的高效递归 (后序遍历)
      • 2.1 代码实现 (方法二)
      • 2.2 效率分析
  • 三、 总结与对比

题目链接: LeetCode 110:判断一棵二叉树是否是“平衡二叉树”,【难度:简单;通过率:59.5%】

这道题不仅考察对二叉树性质的理解,更是展示如何将一个朴素的递归解法优化为高效解法的绝佳案例

一、 题目描述

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

在本题中,一棵高度平衡二叉树定义为:

一个二叉树每个节点的左右两个子树的高度差的绝对值不超过 1

示例:

示例 1:

给定二叉树 [3,9,20,null,null,15,7]3/ \9  20/  \15   7
返回 true

示例 1

示例 2:

给定二叉树 [1,2,2,3,3,null,null,4,4]1/ \2   2/ \3   3/ \4   4
返回 false

示例 2


二、 核心思路分析

题目的关键在于“每个节点”。这意味着我们不能只判断根节点的左右子树高度差,而是要确保树中的所有节点都满足这个条件。这天然地引出了递归的思想

1. 方法一:自顶向下的简单递归

最直观的想法是:

  1. 对于当前节点 root,我们先判断它自身是否平衡。这需要我们分别计算其左子树的高度和右子树的高度,然后看它们的差值是否小于等于 1
  2. 如果当前节点满足条件,我们不能就此罢休,还必须递归地去判断它的左子树和右子树是否也是平衡的
  3. 只有当当前节点、左子树、右子树都平衡时,整棵树才算平衡

这是一种“自顶向下”的思路,先判断顶层,再深入子层

1.1 代码实现 (方法一,非最优解)

【一种参考代码】:

class Solution {// 主函数:判断树是否平衡public boolean isBalanced(TreeNode root) {// 空树是平衡的if (root == null) {return true;}// 1. 判断当前节点是否平衡if (Math.abs(getDepth(root.left) - getDepth(root.right)) > 1) {return false;}// 2. 递归地判断左右子树是否平衡// 只有当左右子树都平衡时,整棵树才平衡return isBalanced(root.left) && isBalanced(root.right);}// 辅助函数:获取以 root 为根的子树的最大高度public int getDepth(TreeNode root) {if (root == null) {return 0;}return Math.max(getDepth(root.left), getDepth(root.right)) + 1;}
}

1.2 效率分析

但是,这个方法存在严重的重复计算问题:

当我们调用 isBalanced(root) 时,getDepth 会遍历整个左子树和右子树。然后,当我们递归调用 isBalanced(root.left) 时,getDepth 又会再次遍历 root.left 的所有子节点。节点被反复遍历,导致时间效率低下

  • 时间复杂度:对于平衡树是 O(N log N),对于退化成链表的树是 O(N²)
  • 空间复杂度:O(H),递归栈的深度,最坏为 O(N)

下面讲解更好的递归解法

2. 方法二:自底向上的高效递归 (后序遍历)

为了避免重复计算,我们可以换一种思路。能不能在一次递归中,既能获取子树的高度,又能知道它是否平衡呢?

答案是可以的!我们可以采用“自底向上”的思路,这与后序遍历(左-右-根)的顺序不谋而合

  1. 我们先递归到最深的叶子节点
  2. 在回溯(返回)的过程中,我们既计算当前节点的高度,也顺便判断它是否平衡
  3. 我们约定一个特殊的返回值,比如 -1,来表示“这棵子树已经不平衡了”
  4. 如果一个子树的递归调用返回了 -1,我们就知道没必要再继续计算了,直接将 -1 继续向上传递,实现**“剪枝”**

2.1 代码实现 (方法二)

【一种参考代码】:

class Solution {// 主函数:通过调用辅助函数并检查其返回值来判断public boolean isBalanced(TreeNode root) {// 如果 getHeight 返回 -1,说明树不平衡;否则平衡return getHeight(root) != -1;}/*** 辅助函数:在计算高度的同时判断是否平衡* @return 如果以 root 为根的子树是平衡的,则返回其高度;否则返回 -1*/public int getHeight(TreeNode root) {// 基准情况:空节点高度为 0,是平衡的if (root == null) {return 0;}// 1. 递归获取左子树的高度(后序遍历的“左”)int leftHeight = getHeight(root.left);// 如果左子树已经不平衡,立即剪枝,返回 -1if (leftHeight == -1) {return -1;}// 2. 递归获取右子树的高度(后序遍历的“右”)int rightHeight = getHeight(root.right);// 如果右子树已经不平衡,立即剪枝,返回 -1if (rightHeight == -1) {return -1;}// 3. 处理当前节点(后序遍历的“根”)// 判断当前节点是否平衡if (Math.abs(leftHeight - rightHeight) > 1) {// 如果不平衡,返回 -1return -1;} else {// 如果平衡,返回当前子树的真实高度return Math.max(leftHeight, rightHeight) + 1;}}
}

2.2 效率分析

通过这种自底向上的后序遍历方式,每个节点只被访问一次。我们在计算高度的同时完成了平衡性的判断

  • 时间复杂度O(N),因为每个节点只访问一次
  • 空间复杂度:O(H),递归栈的深度,最坏为 O(N)

三、 总结与对比

特性方法一 (自顶向下)方法二 (自底向上 / 后序遍历)
思路先判断当前节点,再递归判断子节点先递归处理子节点,再根据子节点信息判断当前节点
遍历顺序类似前序遍历后序遍历
效率低,存在大量重复计算高,每个节点只访问一次
时间复杂度O(N log N) ~ O(N²)O(N)
关键点分别用 isBalancedgetDepth 两个递归函数用一个递归函数 getHeight 和特殊返回值 -1 完成所有工作

当发现一个“自顶向下”的解法存在重复计算时,不妨尝试一下“自底向上”的思路,看看能否在**回溯时“顺便”**解决问题

http://www.dtcms.com/a/298218.html

相关文章:

  • 渗透高级-----测试复现(第三次作业)
  • 【GoLang#2】:基础入门(工具链 | 基础语法 | 内置函数)
  • 基于STM32汽车自动智能雨刷检测雨滴系统设计
  • 汽车免拆诊断案例 | 2010款奔驰E200 CGI车EPS OFF灯异常点亮
  • 数据类型选择:存储效率与查询性能的平衡
  • 【redis】缓存穿透、缓存击穿、缓存雪崩区别
  • Java学习第七十三部分——Redis
  • 前端面试问题
  • 安卓小说阅读软件推荐
  • 深入解析HBase如何保证强一致性:WAL日志与MVCC机制
  • [尚庭公寓]14-找房模块
  • 手写A2C(FrozenLake环境)
  • 直播美颜SDK动态贴纸模块开发指南:从人脸关键点识别到3D贴合
  • kiro的介绍和安装
  • 7.文件操作:让程序读写文件 [特殊字符]
  • CentOS 7.9 + GCC9 离线安装 IWYU(Include What You Use)
  • Linux库——库的制作和原理(1)_回顾动静态库、制作使用库
  • 【服务器与部署 26】配置管理实战:Ansible、Puppet自动化配置管理让运维效率提升10倍
  • 电磁兼容二:共模和差模问题
  • 【06】C#入门到精通——C# 多个 .cs文件项目 同一项目下添加多个 .cs文件
  • Spring Boot 整合 MyBatis 与 Druid 数据源全流程
  • 《整合Spring Cache:本地缓存、Redis与Caffeine对比实践》
  • 7.25总结
  • 详解Python标准库之内置函数
  • 20255年第四届创新杯(原钉钉杯)参考论文+标准答案发布
  • 融合与智能:AI 时代数据库的进化之路
  • Low DK(低介电常数)板材的好处:
  • Vue2上
  • 《Uniapp-Vue 3-TS 实战开发》自定义年月日时分秒picker组件
  • Linux之shell脚本篇(二)