【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)

相关推荐
avocado_green2 小时前
【LeetCode】90. 子集 II
算法·leetcode
6Hzlia3 小时前
【Hot 100 刷题计划】 LeetCode 300. 最长递增子序列 | C++ 动态规划 & 贪心二分
c++·leetcode·动态规划
6Hzlia3 小时前
【Hot 100 刷题计划】 LeetCode 72. 编辑距离 | C++ 经典 DP 增删改状态转移
c++·算法·leetcode
穿条秋裤到处跑3 小时前
每日一道leetcode(2026.04.16):距离最小相等元素查询
算法·leetcode·职场和发展
小雅痞4 小时前
[Java][Leetcode simple] 1. 两数之和
java·算法·leetcode
lolo大魔王4 小时前
Go语言的函数与指针的定义
开发语言·后端·golang
codeejun5 小时前
每日一Go-51、Go微服务--API网关-Kong
微服务·golang·kong
6Hzlia5 小时前
【Hot 100 刷题计划】 LeetCode 51. N 皇后 | C++ 回溯算法&状态数组
c++·算法·leetcode
梦想与想象-广州大智汇5 小时前
告别“内存刺客”!sync-canal-go:轻量mysql实时同步数据到Elasticsearch‌,clickhouse,redis
mysql·elasticsearch·golang·同步数据