二叉树最近公共祖先(LCA)问题解析与实现
1. 项目概述二叉树最近公共祖先问题在二叉树相关算法中最近公共祖先Lowest Common Ancestor简称LCA是一个经典且高频出现的面试题。LeetCode第236题正是考察这个知识点题目要求给定一个二叉树和其中的两个节点找到这两个节点的最近公共祖先。这里的最近指的是在二叉树中深度最大的公共祖先节点。这个问题在实际开发中有诸多应用场景比如在版本控制系统中寻找两个分支的最近合并点在DOM树中查找两个元素的共同父节点或者在家族关系系统中计算两个人的最近共同祖先等。理解并掌握这个问题的解法不仅能帮助我们应对技术面试更能提升我们处理树形结构数据的思维能力。2. 核心概念解析2.1 二叉树基础回顾二叉树是每个节点最多有两个子节点的树结构通常称为左子节点和右子节点。在解决LCA问题时我们需要明确几个关键概念节点深度从根节点到该节点的路径长度祖先节点从根节点到该节点的路径上的所有节点都是其祖先公共祖先同时是两个节点祖先的节点最近公共祖先距离两个节点最近的公共祖先节点2.2 最近公共祖先的定义最近公共祖先是指在一个树结构中两个给定节点的所有公共祖先中距离这两个节点最近的那个节点。换句话说它是这两个节点在树中交汇的第一个点。举个例子考虑以下二叉树3 / \ 5 1 / \ / \ 6 2 0 8 / \ 7 4节点5和1的LCA是3节点5和4的LCA是5节点7和8的LCA是33. 递归解法详解3.1 递归思路分析递归是解决树形结构问题的天然工具因为树本身就是递归定义的数据结构。对于LCA问题我们可以采用后序遍历左右根的方式自底向上地寻找公共祖先。核心思路是如果当前节点是p或q中的一个则返回当前节点分别在左右子树中递归查找p和q如果左右子树都返回非空节点说明当前节点就是LCA如果只有一边返回非空节点则返回该节点说明LCA在子树中3.2 递归实现代码class TreeNode: def __init__(self, x): self.val x self.left None self.right None class Solution: def lowestCommonAncestor(self, root: TreeNode, p: TreeNode, q: TreeNode) - TreeNode: # 基准情况如果root为空或者root就是p或q直接返回root if not root or root p or root q: return root # 递归在左子树中查找 left self.lowestCommonAncestor(root.left, p, q) # 递归在右子树中查找 right self.lowestCommonAncestor(root.right, p, q) # 如果左右都找到了说明当前root就是LCA if left and right: return root # 如果只有一边找到返回找到的那边 return left if left else right3.3 递归过程图解让我们以之前的二叉树为例查找节点5和1的LCA从根节点3开始递归进入左子树5在节点5发现匹配p(5)返回5回到节点3递归进入右子树1在节点1发现匹配q(1)返回1在节点3左右子树都返回非空因此3是LCA4. 算法复杂度分析4.1 时间复杂度该算法需要访问二叉树中的每个节点一次因此时间复杂度为O(N)其中N是二叉树中的节点数量。这是最优的时间复杂度因为我们必须检查每个节点才能确定LCA。4.2 空间复杂度空间复杂度主要取决于递归调用的栈深度。在最坏情况下树退化为链表空间复杂度为O(N)。在平衡二叉树的情况下空间复杂度为O(logN)。5. 边界条件与特殊情况处理5.1 节点不存在的情况在实际应用中我们需要考虑p或q可能不在树中的情况。上述基础解法假设两个节点都在树中。如果需要处理节点不存在的情况可以修改算法class Solution: def lowestCommonAncestor(self, root: TreeNode, p: TreeNode, q: TreeNode) - TreeNode: self.found_p False self.found_q False result self.findLCA(root, p, q) return result if (self.found_p and self.found_q) else None def findLCA(self, root, p, q): if not root: return None left self.findLCA(root.left, p, q) right self.findLCA(root.right, p, q) # 检查当前节点是否是p或q if root p: self.found_p True return root if root q: self.found_q True return root if left and right: return root return left if left else right5.2 其他边界情况当p就是q的祖先时应该返回p当q就是p的祖先时应该返回q当树为空时应该返回None当p或q为None时应该返回None6. 非递归解法对比6.1 使用父指针的迭代方法虽然递归解法简洁优雅但在某些情况下比如树非常深时我们可能需要考虑迭代解法。一种常见的方法是使用父指针从根节点开始遍历树记录每个节点的父指针从p开始向上访问所有祖先存入集合从q开始向上访问祖先第一个在集合中的就是LCAclass Solution: def lowestCommonAncestor(self, root: TreeNode, p: TreeNode, q: TreeNode) - TreeNode: stack [root] parent {root: None} # 迭代直到找到p和q的父指针 while p not in parent or q not in parent: node stack.pop() if node.left: parent[node.left] node stack.append(node.left) if node.right: parent[node.right] node stack.append(node.right) # 收集p的所有祖先 ancestors set() while p: ancestors.add(p) p parent[p] # 查找q的祖先中第一个在p的祖先集合中的节点 while q not in ancestors: q parent[q] return q6.2 两种方法的比较方法时间复杂度空间复杂度适用场景递归O(N)O(H)代码简洁树深度不大时迭代父指针O(N)O(N)树很深可能栈溢出时7. 实际应用与变种问题7.1 实际应用场景版本控制系统Git中寻找两个分支的最近共同提交DOM操作查找两个HTML元素的最近共同父元素计算生物学在系统发育树中寻找物种的最近共同祖先社交网络计算两个人的最近共同好友或关系7.2 常见变种问题二叉搜索树的LCA利用BST性质可以更高效地解决多叉树的LCA原理类似但需要考虑多个子节点带父指针的树的LCA可以转化为链表相交问题多个节点的LCA扩展为寻找多个节点的最近公共祖先8. 常见错误与调试技巧8.1 新手常见错误混淆节点值比较和节点比较应该比较节点对象而非节点值错误if root.val p.val正确if root p忽略递归基准条件忘记处理root为None的情况错误理解最近返回了第一个找到的公共祖先而非最近的未考虑节点不在树中的情况当p或q不在树中时应返回None8.2 调试技巧可视化递归过程画出递归调用树标注每次递归的返回值打印调试信息在递归函数中添加打印语句显示当前节点和递归深度使用小型测试用例先从简单的3节点树开始测试边界测试测试p或q是根节点、p是q的祖先等情况9. 性能优化与进阶思考9.1 多次查询优化如果需要多次查询不同节点对的LCA可以考虑预处理技术欧拉序RMQ将LCA问题转化为RMQ问题Tarjan离线算法一次性处理所有查询二进制提升法预处理每个节点的2^k级祖先这些方法可以将单次查询时间优化到O(1)或O(logN)但需要额外的预处理时间和空间。9.2 非二叉树扩展对于一般的树结构不一定是二叉树LCA问题同样适用。常用的解法包括转化为RMQ问题通过DFS遍历记录欧拉序和深度序列使用并查集Tarjan离线算法的核心树链剖分将树分解为多条链加速查询10. 面试技巧与实战建议10.1 面试中的解题步骤明确问题确认输入输出询问边界条件节点是否一定存在树是否可能为空举例说明画一个小型例子手动计算LCA提出暴力解法先给出直观解法如记录路径然后比较优化思路分析暴力解法的问题引出递归/迭代优化代码实现编写清晰、模块化的代码测试验证用多个测试用例验证代码正确性10.2 常见面试问题如何证明你的算法是正确的如果树非常大递归解法会有什么问题如何修改算法处理节点可能不存在的情况在二叉搜索树中如何更高效地解决这个问题如果每个节点都有指向父节点的指针如何优化解法11. 相关题目推荐为了巩固对LCA问题的理解建议练习以下LeetCode题目235. 二叉搜索树的最近公共祖先利用BST性质优化1644. 二叉树的最近公共祖先 II处理节点可能不存在的情况1650. 二叉树的最近公共祖先 III节点有父指针的情况1676. 二叉树的最近公共祖先 IV查找多个节点的LCA1123. 最深叶节点的最近公共祖先LCA变种问题12. 个人经验分享在实际面试和刷题过程中我发现LCA问题有几个关键点需要特别注意递归终止条件一定要先处理root为None或root等于p/q的情况这个顺序不能错返回值理解递归函数返回的不是最终的LCA而是表示当前子树中是否包含p或q测试用例设计要包括p和q在不同侧、同侧、一个是另一个祖先等情况空间优化在面试中如果被问到可以讨论如何用迭代替代递归避免栈溢出一个容易忽略的细节是当p就是q的祖先时算法应该返回p而不是继续向上查找。这在递归解法中是自然处理的但在某些迭代实现中可能需要特殊处理。

相关新闻

数字化3.0时代:从大模型智能涌现到AI工程实践落地

数字化3.0时代:从大模型智能涌现到AI工程实践落地

1. 从“数字化3.0”到“智能涌现”:我们正站在怎样的路口? 最近和几个做企业服务和技术投资的朋友聊天,大家不约而同地提到一个词:“数字化3.0”。这个词听起来有点宏大叙事,但背后其实是我们每天都能感受到的、正在发…

2026/10/4 20:39:35 阅读更多 →
从被动镜像到主动智能体:面向网络物理人工智能的整体论数字孪生(HDT-Net)

从被动镜像到主动智能体:面向网络物理人工智能的整体论数字孪生(HDT-Net)

从被动镜像到主动智能体:面向网络物理人工智能的整体论数字孪生(HDT-Net) 论文原网页:https://arxiv.org/html/2608.06227v1 摘要 大模型、生成式AI在医疗、文娱领域已落地,但机器人、自动驾驶等物理AI系统部署时仍…

2026/10/4 20:39:35 阅读更多 →
3分钟免费获取通达信实时行情数据:Python量化交易终极指南

3分钟免费获取通达信实时行情数据:Python量化交易终极指南

3分钟免费获取通达信实时行情数据:Python量化交易终极指南 【免费下载链接】mootdx 通达信数据读取的一个简便使用封装 项目地址: https://gitcode.com/GitHub_Trending/mo/mootdx 想要用Python进行量化交易却苦于金融数据接口昂贵且复杂?MOOTDX为…

2026/10/4 20:39:36 阅读更多 →

最新新闻

GPU利用率低?真正瓶颈可能在数据供给或内存访问

GPU利用率低?真正瓶颈可能在数据供给或内存访问

1. 为什么“GPU利用率低”是个伪命题?——先破再立的诊断思维你有没有遇到过这样的场景:训练一个中等规模的Transformer模型,nvidia-smi里显示GPU显存占了85%,但gpu-util却长期卡在12%~18%之间,像一台被塞满…

2026/10/7 5:55:26 阅读更多 →
OpenAI Codex实战:终端里的AI编程智能体从安装到自动化任务

OpenAI Codex实战:终端里的AI编程智能体从安装到自动化任务

1. 20多项更新里,为什么偏偏是Codex值得细看1.1 其他更新是什么量级,Codex是什么量级OpenAI DevDay 一口气发了20多项更新,从GPT-5系列模型的API开放,到Realtime API、多模态能力的升级,再到各种Agent工具的补完&#…

2026/10/7 5:55:26 阅读更多 →
机械臂运动学从入门到实战:DH参数、正逆解与UR5e仿真详解

机械臂运动学从入门到实战:DH参数、正逆解与UR5e仿真详解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 5:55:26 阅读更多 →
Claude Code Router 安装与使用全流程:Windows 与 Linux 双平台配置 TaoToken 统一 Key

Claude Code Router 安装与使用全流程:Windows 与 Linux 双平台配置 TaoToken 统一 Key

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 5:55:26 阅读更多 →
一个人,n个AI大臣:我在天翼云电脑OpenClaw建立的“大周王朝“职场生存指南

一个人,n个AI大臣:我在天翼云电脑OpenClaw建立的“大周王朝“职场生存指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 5:55:26 阅读更多 →
XPay原理与部署:Java个人收款到账监听与回调实现

XPay原理与部署:Java个人收款到账监听与回调实现

简介:XPay V3.1是一套基于Java开发的个人收款支付系统,专为个人站长、独立开发者及小微企业设计,完全免费且无需签约,生成个人收款码后即可收款,交易资金直接进入本人账户,有效破解传统支付接入门槛高、结算…

2026/10/7 5:54:25 阅读更多 →

日新闻

ROS2机械臂仿真与运动控制:从URDF建模到Gazebo实战全解析

ROS2机械臂仿真与运动控制:从URDF建模到Gazebo实战全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 1:01:58 阅读更多 →
用浏览器直接改ESP32的WiFi密码:NVS键值配置工具设计与实现

用浏览器直接改ESP32的WiFi密码:NVS键值配置工具设计与实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 1:02:00 阅读更多 →
芯片封装缺陷检测:扫描声学显微镜(SAT)原理与实操指南

芯片封装缺陷检测:扫描声学显微镜(SAT)原理与实操指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 1:02:00 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/6 7:15:40 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/6 5:29:09 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/6 6:26:51 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/6 8:21:32 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/6 4:21:51 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/6 1:18:13 阅读更多 →