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

输入 :root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出 :3
解释:节点 5 和节点 1 的最近公共祖先是节点 3 。
示例 2 :

输入 :root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出 :5
解释:节点 5 和节点 4 的最近公共祖先是节点 5 。因为根据定义最近公共祖先节点可以为节点本身。
示例 3 :
输入 :root = [1,2], p = 1, q = 2
输出:1
提示 :
树中节点数目在范围 [2, 10^5] 内。
-10^9 <= Node.val <= 10^9
所有 Node.val 互不相同 。
p != q
p 和 q 均存在于给定的二叉树中。
思路:
用后序遍历(左→右→根),从下往上找:
- 如果当前节点是
nil/ 等于p/ 等于q,直接返回当前节点 - 递归查左子树 和右子树
- 分三种情况判断:
- 左、右都不为空 → 当前节点就是 LCA
- 左为空,右不为空 → 答案在右边
- 右为空,左不为空 → 答案在左边
一句话总结:哪边能找到目标节点就返回哪边,两边都能找到就返回自己。
代码实现(Go):
go
package main
import "fmt"
type TreeNode struct {
Val int
Left *TreeNode
Right *TreeNode
}
// 参数:root 二叉树根节点;p、q 是树中两个需要查找祖先的节点
// 返回值:找到的最近公共祖先节点
func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode {
// ===================== 递归终止条件 1 =====================
// 如果当前遍历到的节点是空节点(走到了叶子节点的尽头)
// 说明这条路径上没有找到 p 或 q,直接返回 nil
if root == nil {
return nil
}
// ===================== 递归终止条件 2 =====================
// 如果当前节点的值 等于 p 的值 或者 等于 q 的值
// 说明我们找到了目标节点本身,直接返回当前节点
// 这是因为:一个节点可以是它自己的祖先
if root.Val == p.Val || root.Val == q.Val {
return root
}
// ===================== 递归遍历左右子树 =====================
// 递归查找【左子树】,获取左子树的查找结果
// left 不为 nil:说明左子树里找到了 p 或 q
// left 为 nil:说明左子树里没找到
left := lowestCommonAncestor(root.Left, p, q)
// 递归查找【右子树】,获取右子树的查找结果
right := lowestCommonAncestor(root.Right, p, q)
// ===================== 根据结果判断公共祖先 =====================
// 情况 1:左、右两边都找到了(left≠nil 且 right≠nil)
// 说明 p 在左子树、q 在右子树,或者反过来
// 那么当前这个节点,就是 p 和 q 的【最近公共祖先】
if left != nil && right != nil {
return root
}
// 情况 2:右边没找到(right == nil)
// 说明 p 和 q 都在左子树里,答案就是 left
if right == nil {
return left
}
// 情况 3:左边没找到(left == nil)
// 说明 p 和 q 都在右子树里,答案就是 right
return right
}
func main() {
// 树的结构如下:
// 3
// / \
// 5 1
// / \ / \
// 6 2 0 8
// / \
// 7 4
root := &TreeNode{Val: 3}
root.Left = &TreeNode{Val: 5}
root.Right = &TreeNode{Val: 1}
root.Left.Left = &TreeNode{Val: 6}
root.Left.Right = &TreeNode{Val: 2}
root.Right.Left = &TreeNode{Val: 0}
root.Right.Right = &TreeNode{Val: 8}
root.Left.Right.Left = &TreeNode{Val: 7}
root.Left.Right.Right = &TreeNode{Val: 4}
// ===================== 测试示例 1 =====================
// p = 5,q = 1 函数参数需要的是节点(结构体指针)
p1 := root.Left
q1 := root.Right
// 预期输出:3
fmt.Println(lowestCommonAncestor(root, p1, q1).Val)
// ===================== 测试示例 2 =====================
// p = 5,q = 4
p2 := root.Left
q2 := root.Left.Right.Right
// 预期输出:5
fmt.Println(lowestCommonAncestor(root, p2, q2).Val)
}
- 时间复杂度:O(n)
遍历整棵二叉树,每个节点只访问一次 - 空间复杂度:O(n)
递归调用栈的深度,最坏情况(链状树)是 O(n)