摘要
最近公共祖先(LCA,Lowest Common Ancestor)在二叉树、二叉搜索树(BST)等数据结构中有广泛应用,比如权限管理、网络路由、基因分析等。今天我们用 Swift 来解 LeetCode 236:「二叉树的最近公共祖先」,不仅会给出代码,还会分析它的时间复杂度、空间复杂度,并结合实际场景聊聊它的应用。
问题描述
给定一个二叉树,找到两个节点的最近公共祖先(LCA)。
LCA 的定义:
“对于两个节点 p 和 q,它们的最近公共祖先是树中最深的一个节点,该节点的子树同时包含 p 和 q(允许某个节点是它自己的祖先)。”
示例 1
输入
root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
二叉树结构
3
/ \
5 1
/ \ / \
6 2 0 8
/ \
7 4
输出
3
解释:节点 3 是 5 和 1 的最近公共祖先。
示例 2
输入
root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
二叉树结构
3
/ \
5 1
/ \ / \
6 2 0 8
/ \
7 4
输出
5
解释:5 是 5 和 4 的最近公共祖先,因为 4 是 5 的子节点。
示例 3
输入
root = [1,2], p = 1, q = 2
二叉树结构
1
/
2
输出
1
解释:1 是 1 和 2 的最近公共祖先。
Swift 题解
我们可以使用递归的方法来求解 LCA。核心思路是:
- 递归遍历整棵树,如果当前节点
root
是p
或q
,那么root
可能就是 LCA。 - 递归到左右子树寻找 p 和 q:
- 如果
p
和q
分别出现在左右子树,说明当前节点root
就是最近公共祖先。 - 如果
p
和q
只出现在某一侧(左或右),说明最近公共祖先在那一侧。
- 如果
代码实现:
class TreeNode {
var val: Int
var left: TreeNode?
var right: TreeNode?
init(_ val: Int) {
self.val = val
self.left = nil
self.right = nil
}
}
class Solution {
func lowestCommonAncestor(_ root: TreeNode?, _ p: TreeNode?, _ q: TreeNode?) -> TreeNode? {
// 如果 root 为空,或者 root 等于 p 或 q,直接返回 root
if root == nil || root === p || root === q {
return root
}
// 递归查找左右子树
let left = lowestCommonAncestor(root?.left, p, q)
let right = lowestCommonAncestor(root?.right, p, q)
// 如果左右子树都找到 p 或 q,说明当前 root 就是 LCA
if left != nil && right != nil {
return root
}
// 否则,LCA 在找到的那一侧
return left != nil ? left : right
}
}
代码分析
递归逻辑
lowestCommonAncestor
方法会递归遍历二叉树,寻找p
和q
。- 终止条件:
- 如果
root == nil
,返回nil
,表示没找到节点。 - 如果
root == p
或root == q
,直接返回root
,表示找到了其中一个目标节点。
- 如果
- 递归查找:
- 在
root.left
和root.right
中分别查找p
和q
。 - 如果
p
和q
分别位于root
的左右子树,那么root
就是最近公共祖先。 - 如果
p
和q
只在某一侧,则返回该侧的结果。
- 在
示例测试
测试代码
func testLCA() {
let root = TreeNode(3)
root.left = TreeNode(5)
root.right = TreeNode(1)
root.left?.left = TreeNode(6)
root.left?.right = TreeNode(2)
root.right?.left = TreeNode(0)
root.right?.right = TreeNode(8)
root.left?.right?.left = TreeNode(7)
root.left?.right?.right = TreeNode(4)
let solution = Solution()
if let lca = solution.lowestCommonAncestor(root, root.left, root.right) {
print("最近公共祖先是: \(lca.val)") // 3
}
if let lca = solution.lowestCommonAncestor(root, root.left, root.left?.right?.right) {
print("最近公共祖先是: \(lca.val)") // 5
}
}
testLCA()
输出
最近公共祖先是: 3
最近公共祖先是: 5
时间复杂度
- 最坏情况:二叉树是一个链表,需要遍历所有节点,时间复杂度是 O(N)。
- 平均情况:每个节点最多被访问一次,仍然是 O(N)。
空间复杂度
- 递归调用栈:最坏情况(退化为链表)需要 O(N) 的栈空间。
- 平均情况:平衡二叉树情况下,递归调用栈深度为 O(log N),因此空间复杂度是 O(log N)。
实际应用场景
1. 权限管理
假设一个公司的组织结构是一个树状结构,查找最近公共祖先就可以用于找出两个员工的最小共同主管。
2. 网络路由
在计算机网络中,数据包需要找到某个路由的最近公共祖先,以决定最优传输路径。
3. 生物学(基因研究)
在家族树或基因进化树中,找到两个生物物种的最近公共祖先,帮助研究它们的共同祖先。
总结
- 这道题是典型的 二叉树递归遍历 题目,核心思想是后序遍历。
- 代码逻辑清晰,时间复杂度 O(N),空间复杂度 O(log N)~O(N)。
- 结合实际场景,LCA 可以用于权限管理、网络路由、基因研究等多个领域。
希望这篇文章对你有所帮助!如果有问题,欢迎交流!