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

【LeetCode 热题 100】236. 二叉树的最近公共祖先——DFS

Problem: 236. 二叉树的最近公共祖先
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

文章目录

  • 整体思路
  • 完整代码
  • 时空复杂度
    • 时间复杂度:O(N)
    • 空间复杂度:O(H)

整体思路

这段代码旨在解决一个经典的树形问题:二叉树的最近公共祖先 (Lowest Common Ancestor of a Binary Tree)。问题要求在一棵普通的二叉树(非二叉搜索树)中,找到两个给定节点 pq 的最近公共祖先(LCA)。

该算法采用了一种非常优雅且高效的 深度优先搜索 (DFS) 的后序遍历 策略。其核心思想是自底向上地传递信息,通过递归函数的返回值来判断LCA的位置。

  1. 递归函数的定义与含义

    • lowestCommonAncestor(root, p, q) 这个递归函数的返回值具有特定的含义:
      • 如果在以 root 为根的子树中能找到 pq(或两者都能找到,但一个是另一个的祖先),函数就返回那个找到的节点 (pq)。
      • 如果在 root 的左右子树中分别找到了 pq,函数就返回 root 本身,因为 root 就是它们的LCA。
      • 如果在以 root 为根的子树中一个都找不到,函数就返回 null
  2. 递归的终止条件(Base Case)

    • if (root == null || root == p || root == q): 这是递归的出口。
      • root == null: 如果当前节点为空,说明这条路径走到底了,什么也没找到,返回 null
      • root == p || root == q: 如果当前节点就是 pq 中的一个,根据我们对函数返回值的定义,说明我们“找到”了目标,直接返回当前节点 root
  3. 递归的分解与合并(后序遍历逻辑)

    • 分解
      • TreeNode left = lowestCommonAncestor(root.left, p, q);:递归地在左子树中查找 pq
      • TreeNode right = lowestCommonAncestor(root.right, p, q);:递归地在右子树中查找 pq
    • 合并(这是后序遍历的核心,在处理完左右子树后进行判断):
      • if (left != null && right != null): 这是最关键的判断。如果 leftright 都不是 null,这意味着 pq 分别位于当前 root 节点的左右子树中。根据LCA的定义,当前 root 就是它们的最近公共祖先。因此,返回 root
      • return left != null ? left : right;: 如果上面那个 if 不成立,说明 pq 不在 root 的两侧。这种情况分为两种:
        • 只有一个子树找到了目标:例如,left 不是 nullrightnull。这说明 pq 都在左子树中,并且 left 返回的是它们中更“高”的那个(即它们的LCA)。我们只需将这个结果继续向上传递即可。所以返回 left。同理,如果只有右子树有结果,就返回 right
        • 两个子树都没找到leftright 都是 null,那么就返回 null
        • 这个三元表达式简洁地处理了这两种情况。

通过这种自底向上的信息传递,算法能够准确地在第一次相遇 pq 的分叉点处确定LCA。

完整代码

// Definition for a binary tree node.
// public class TreeNode {
//     int val;
//     TreeNode left;
//     TreeNode right;
//     TreeNode(int x) { val = x; }
// }class Solution {/*** 查找二叉树中两个给定节点 p 和 q 的最近公共祖先 (LCA)。* @param root 树的根节点* @param p 目标节点一* @param q 目标节点二* @return p 和 q 的最近公共祖先节点*/public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {// 步骤 1: 递归的终止条件 (Base Case)// 如果当前节点为空,或者当前节点就是 p 或 q,则直接返回当前节点。// - root == null: 搜索到了叶子节点的子节点,路径结束,返回 null。// - root == p || root == q: 找到了目标节点之一,返回该节点。if (root == null || root == p || root == q) {return root;}// 步骤 2: 递归分解 (后序遍历的前两步)// 在左子树中递归查找 p 或 q。TreeNode left = lowestCommonAncestor(root.left, p, q);// 在右子树中递归查找 p 或 q。TreeNode right = lowestCommonAncestor(root.right, p, q);// 步骤 3: 结果合并 (后序遍历的最后一步)// 基于左右子树的返回结果,判断当前 root 的角色。// Case 1: p 和 q 分别位于 root 的左右子树中。// 此时,left 返回 p (或其祖先),right 返回 q (或其祖先)。// 那么当前 root 就是它们的最近公共祖先。if (left != null && right != null) {return root;}// Case 2: p 和 q 都在同一侧子树中,或者其中一个是另一个的祖先。// - 如果 left 非空,right 为空,说明 p 和 q 都在左子树,left 就是它们在左子树中的LCA,继续向上传递。// - 如果 right 非空,left 为空,说明 p 和 q 都在右子树,right 就是它们在右子树中的LCA,继续向上传递。// - 如果都为空,说明此子树中没有 p 或 q,返回 null。// 这个三元表达式优雅地处理了以上所有情况。return left != null ? left : right;}
}

时空复杂度

时间复杂度:O(N)

  1. 遍历:该算法本质上是一次深度优先搜索(DFS)。在最坏的情况下,它需要访问树中的每一个节点一次。
  2. 节点操作:对于每个节点,执行的操作(比较、递归调用)都是常数时间的。

综合分析
算法的总时间复杂度与树中的节点数 N 成正比。因此,时间复杂度为 O(N)

空间复杂度:O(H)

  1. 主要存储开销:算法的空间开销主要来自于递归调用栈 (recursion call stack)
  2. 空间大小:递归栈的深度取决于树的高度 H
    • 最坏情况:如果树是一个极度不平衡的链状结构,树的高度 H 会约等于节点数 N。此时,空间复杂度为 O(N)
    • 最好情况(平衡二叉树):如果树是完全平衡的,树的高度 H 约等于 log N。此时,空间复杂度为 O(log N)

综合分析
算法所需的额外空间由递归栈的深度决定,因此空间复杂度为 O(H),其中 H 是二叉树的高度。

参考灵神

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

相关文章:

  • oracle 11g drop user 失败,报错ORA-00600
  • jxORM--编程指南
  • EXPLAIN:你的SQL性能优化透视镜
  • 【Docker-Day 7】揭秘 Dockerfile 启动指令:CMD、ENTRYPOINT、ENV、ARG 与 EXPOSE 详解
  • 软件测试-Bug
  • 最简单的 Android TV 项目示例
  • 【RK3576】【Android14】显示屏MIPI开发调试
  • USB 2.0 vs USB 3.0:全面技术对比与选择指南
  • HuggingFace基础知识和环境安装
  • 如何在 QGIS 中定义/更改坐标系?
  • 吴恩达《AI for everyone》第二周课程笔记
  • Redis 概率型数据结构实战指南
  • 浅谈 Vue 的双向数据绑定
  • 10-day07文本分类
  • 借助AI学习开源代码git0.7之四update-cache
  • 常用框架知识
  • 基于单片机的温湿度报警系统设计与实现
  • 神经网络:池化层
  • 数字图像处理(四:图像如果当作矩阵,那加减乘除处理了矩阵,那图像咋变):从LED冬奥会、奥运会及春晚等等大屏,到手机小屏,快来挖一挖里面都有什么
  • 41.FeignClient整合Sentinel
  • 7-20 关于mysql
  • C#.NET EFCore.BulkExtensions 扩展详解
  • 【机器学习工具】Weights Biases
  • C++string类用法
  • gradle微服务依赖模版
  • OpenTelemetry学习笔记(九):Elastic 对 OTLP 的原生支持
  • 【成品设计】基于STM32的宠物检测系统
  • 状态管理与团队协作 - SRE 的核心关切
  • Flink2.0学习笔记:Table API SQL
  • Ubuntu 24.04 设置静态 IP 的方法