二叉树最近公共祖先(LCA)算法详解与面试应用

发布时间:2026/10/7 11:57:27

二叉树最近公共祖先(LCA)算法详解与面试应用 1. 问题背景与核心概念最近在准备算法面试的同学一定对LeetCode 236题不陌生——二叉树的最近公共祖先。这道题在各大科技公司的面试中出现频率极高据不完全统计在近6个月的面试中考察率超过40%。我当年面试时就曾被这道题卡住后来专门花了三天时间研究了所有可能的解法。所谓最近公共祖先(Lowest Common Ancestor, LCA)指的是二叉树中两个节点p和q最近的共同祖先节点。举个例子假设我们有一个家族树你想知道自己和表妹最近的共同祖先是谁这个问题就类似于在二叉树中寻找LCA。2. 基础解法递归法2.1 递归思路解析最直观的解法是使用递归。算法思路其实很符合直觉如果当前节点是p或q那么它就是LCA分别在左右子树中查找p和q如果p和q分别位于左右子树当前节点就是LCA如果只有一边找到返回找到的那边def lowestCommonAncestor(root, p, q): if not root or root p or root q: return root left lowestCommonAncestor(root.left, p, q) right lowestCommonAncestor(root.right, p, q) if left and right: return root return left if left else right2.2 时间复杂度分析这个解法的时间复杂度是O(n)因为最坏情况下需要遍历所有节点。空间复杂度是O(h)h是树的高度由递归栈深度决定。注意递归解法虽然简单但在面试中往往会被追问更优的解法所以我们需要掌握其他方法。3. 进阶解法父指针哈希表法3.1 算法原理这个方法分为两个步骤使用DFS遍历树记录每个节点的父节点从p节点开始向上回溯到根节点记录路径从q节点开始向上回溯第一个出现在p路径中的节点就是LCAdef lowestCommonAncestor(root, p, q): parent {root: None} stack [root] 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) ancestors set() while p: ancestors.add(p) p parent[p] while q not in ancestors: q parent[q] return q3.2 适用场景这种方法特别适合需要多次查询LCA的情况因为我们可以预先建立好父指针哈希表后续查询只需要O(h)时间。4. 高阶解法RMQ转化法4.1 欧拉序与RMQ这是比较高级的解法将LCA问题转化为RMQ区间最小值查询问题对树进行欧拉遍历记录访问顺序和深度LCA问题转化为在欧拉序列中找两个节点首次出现位置之间的最小深度节点class Solution: def __init__(self): self.euler [] self.depth [] self.first_occurrence {} def lowestCommonAncestor(self, root, p, q): self.dfs(root, 0) self.build_sparse_table() # 转换为RMQ查询 # ...省略RMQ实现部分... def dfs(self, node, current_depth): if not node: return self.first_occurrence[node.val] len(self.euler) self.euler.append(node.val) self.depth.append(current_depth) # ...继续遍历左右子树...4.2 性能分析预处理时间O(n)查询时间O(1)。适合需要大量查询的场景但实现较为复杂。5. Tarjan离线算法5.1 算法思想Tarjan算法是一种并查集应用的离线算法使用DFS遍历树将已访问但未处理完的节点标记为正在访问处理完的节点使用并查集合并到父节点查询与当前节点相关的所有问题def tarjan_olca(root, queries): # 初始化并查集 parent {node: node for node in tree_nodes} rank {node: 0 for node in tree_nodes} ancestor {} visited set() def find(u): while parent[u] ! u: parent[u] parent[parent[u]] u parent[u] return u def union(u, v): u_root find(u) v_root find(v) if u_root v_root: return if rank[u_root] rank[v_root]: parent[v_root] u_root else: parent[u_root] v_root if rank[u_root] rank[v_root]: rank[v_root] 1 def dfs(node): visited.add(node) ancestor[node] node for child in [node.left, node.right]: if child and child not in visited: dfs(child) union(node, child) ancestor[find(node)] node for v in queries.get(node, []): if v in visited: print(fLCA of {node} and {v} is {ancestor[find(v)]}) dfs(root)5.2 适用场景当需要处理大量离线查询时Tarjan算法非常高效时间复杂度接近O(nα(n))其中α是反阿克曼函数。6. 迭代法使用栈模拟递归6.1 实现思路对于不喜欢递归或者担心栈溢出的情况可以使用显式栈来模拟递归过程def lowestCommonAncestor(root, p, q): stack [root] parent {root: None} 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) ancestors set() while p: ancestors.add(p) p parent[p] while q not in ancestors: q parent[q] return q6.2 性能对比这种方法与递归法时间复杂度相同但避免了递归的系统开销适合深度很大的树。7. 各方法对比与选择建议方法时间复杂度空间复杂度适用场景实现难度递归法O(n)O(h)单次查询树不深★★父指针法O(n)O(n)多次查询★★★RMQ转化O(n)预处理O(1)查询O(nlogn)大量查询★★★★★Tarjan离线O(nα(n))O(n)离线批量查询★★★★迭代法O(n)O(h)避免递归树不深★★★在实际面试中我建议按照以下顺序展示先给出递归解法最简单然后给出父指针法中等难度如果面试官继续追问再讨论RMQ或Tarjan8. 常见错误与调试技巧空指针问题总是检查节点是否为null特别是在处理左右子树时节点相等情况当p就是q的祖先时容易忽略直接返回p的情况重复计算在递归解法中避免对同一子树重复计算路径记录错误在使用父指针法时确保正确记录和比较路径调试时可以构造以下测试用例p或q就是根节点p是q的祖先p和q在不同子树p和q在相同子树树为空或只有一个节点9. 实际工程中的应用虽然LCA问题看起来是纯算法题但在实际工程中有重要应用Git中寻找两个分支的最近共同提交DOM树中寻找两个元素的最近共同容器网络路由中寻找两个节点的最近交汇点生物信息学中寻找物种进化树中的共同祖先理解这些应用场景可以帮助我们在面试中更好地解释算法的实际价值。10. 扩展思考如果问题扩展到N叉树或者需要处理大量查询上述哪些方法仍然适用实际上递归法可以很容易扩展到N叉树父指针法同样适用RMQ方法需要调整欧拉遍历方式Tarjan算法基本保持不变对于超大规模树的处理可以考虑使用分布式算法或者结合树链剖分等高级技巧。
延伸阅读

更多相关文章

2026/10/7 5:30:39

一汽-大众500公里纯电SUV技术解析:MEB平台与三电系统深度剖析

1. 从“油改电”到原生纯电:一汽-大众的电动化转身最近看到一汽-大众要推出一款续航500公里、定位与奥迪Q3同级的纯电动SUV,说实话,作为一个在汽车行业摸爬滚打多年的从业者,我的第一反应是“终于来了”。这不仅仅是一款新车的发布…

2026/10/8 2:17:30

Shell脚本一键部署K8s:Docker容器化集群实战

简介:面向需要快速搭建Kubernetes集群的运维与开发人员,这是一套基于Docker容器化的Shell脚本部署方案,覆盖Master与Node节点的初始化、安装、网络插件配置及集群卸载流程。脚本内置docker 24.0.7、cri-dockerd 0.3.9、Kubernetes v1.28.2等版…

2026/10/8 2:17:30

VC异步多线程Socket实战:从WSAAsyncSelect到IOCP的避坑指南

简介:面向VC开发者的异步多线程Socket通信示例工程,同时包含服务端与客户端两套完整项目,适合正在学习网络编程、并发处理及事件驱动模型的初中级开发者参考。工程重点演示Winsock、CAsyncSocket等关键组件的配合,以及OnAccept、O…

2026/10/8 2:17:30

VCam_v5.0含sn.rar:虚拟摄像头安装部署与故障排查指南

简介:VCam_v5.0含sn.rar是一份面向需要虚拟摄像头功能的Windows用户的实用工具包,内置VCam 5.0主程序及对应序列号,适用于网课直播、视频会议、游戏推流等场景。在没有物理摄像头或希望保护隐私时,用户可以播放本地视频、桌面内容…

2026/10/8 2:17:30

工业电源路径可靠性设计:TPS259483AYWPR与PIC18F4682协同保护方案

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

2026/10/8 2:12:30

IEEE 802标准速查手册:从协议文本到工程落地的实战指南

简介:本资源是一份系统梳理IEEE 802局域网标准体系的权威中文文档,面向网络工程初学者、通信专业学生及备考软考/思科认证的技术人员,解决对IEEE 802系列标准脉络不清、子标准功能混淆、协议定位不明等核心痛点。文档完整覆盖IEEE 802.1至802…

2026/10/5 6:32:56

Jev+Agent接管浏览器:browser-use实战与jev-ultrafast性能优化

1. 从“Jev”说起:为什么我要把Agent接进浏览器“Jev”这个词最近在圈子里出现的频率越来越高,很多人第一次听到会以为是某个新模型的名字,其实它更像是一种思路——把Jev模型的能力当作底座,通过Agent的方式去接管浏览器&#xf…

2026/10/7 8:18:33

多智能体集群实战:DeepAgents编排、MCP与A2A协议及Skills体系

1. 从"单兵作战"到"集群协同":多智能体编排到底在解决什么问题如果你最近在折腾 Agent 相关的东西,大概率会有一种感觉:单个 Agent 能做的事情,其实很快就摸到天花板了。你给它一个提示词,挂几个工…

2026/10/6 17:46:51

无源低通滤波器设计实战:从RC到LC,手把手教你避开那些坑

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

2026/10/8 0:02:17

自然数立方等于连续奇数之和:从证明到编程验证

十几年来我一直游走在数学科普和编程教学这两块内容之间,对“看起来像魔法、拆开全是数学”的结论总是格外敏感。最近翻资料时又撞见一句话:任何一个自然数 m 的立方,都可以写成 m 个连续奇数之和。2 的立方等于 3 加 5,3 的立方等…

2026/10/8 0:02:17

C#上位机SSH连接实战:用SSH.NET补齐超时、批量与密钥认证

简介:这是一份基于 C# 开发的 SSH 连接功能半成品工程,原本作为另一个主项目的子功能模块,现独立打包分享。工程采用 WinForms 界面,包含源码、解决方案、安装部署工程、NuGet 依赖包及说明文档,适合正在做远程连接、网…

2026/10/8 0:02:17

Java SpringBoot一体化智能售后系统设计与实现全解析

毕业设计年年做,Java Web 方向的题目翻来覆去就那么几个,但“一体化智能售后系统”这个题,每次看到我都觉得值得认真聊一聊。它不是一个简单 curd 堆出来的管理系统,而是把客户、工单、派单、处理、回访、统计整条链路串起来的一套…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑