【Day49】236.二叉树的最近公共祖先

文章目录

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 均存在于给定的二叉树中。


思路:

后序遍历(左→右→根),从下往上找:

  1. 如果当前节点是 nil / 等于 p / 等于 q,直接返回当前节点
  2. 递归查左子树右子树
  3. 分三种情况判断:
    • 左、右都不为空 → 当前节点就是 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)

相关推荐
ulias2121 小时前
leetcode热题 - 6
linux·算法·leetcode
li星野2 小时前
栈与队列通关八题:从括号匹配到接雨水,手撕LeetCode高频题(Python + C++)
c++·python·leetcode
风筝在晴天搁浅2 小时前
字节 LeetCode CodeTop 912.排序数组
算法·leetcode
alphaTao4 小时前
LeetCode 每日一题 2026/5/4-2026/5/10
算法·leetcode·职场和发展
Achou.Wang4 小时前
go 语言条件变量和信号量
golang
Tisfy4 小时前
LeetCode 3629.通过质数传送到达终点的最少跳跃次数:埃式筛+BFS
算法·leetcode·宽度优先·质数·埃式筛
大大杰哥5 小时前
leetcode hot100(2)双指针,滑动窗口
数据结构·算法·leetcode
平凡但不平庸的码农5 小时前
Go 语言基础语法
开发语言·后端·golang
风筝在晴天搁浅5 小时前
LeetCode CodeTop 113.路径总和Ⅱ
算法·leetcode
水木流年追梦5 小时前
【python因果库实战26】逆概率加权模型1
开发语言·python·算法·leetcode