235. 二叉搜索树的最近公共祖先

235. 二叉搜索树的最近公共祖先

235. 二叉搜索树的最近公共祖先

给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 pq,最近公共祖先表示为一个结点 x,满足 xpq 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

例如,给定如下二叉搜索树: root = [6,2,8,0,4,7,9,null,null,3,5]

在这里插入图片描述

示例 1:

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

示例 2:

输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4
输出: 2
解释: 节点 2 和节点 4 的最近公共祖先是 2, 因为根据定义最近公共祖先节点可以为节点本身。

说明:

  • 所有节点的值都是唯一的。
  • p、q 为不同节点且均存在于给定的二叉搜索树中。

思路

做过二叉树:236. 二叉树的最近公共祖先题目的同学应该知道,利用回溯从底向上搜索,遇到一个节点的左子树里有p,右子树里有q,那么当前节点就是最近公共祖先。

那么本题是二叉搜索树,二叉搜索树是有序的,那得好好利用一下这个特点。

在有序树里,如果判断一个节点的左子树里有p,右子树里有q呢?

因为是有序树,所以 如果 中间节点是 qp 的公共祖先,那么 中节点的数值 一定是在 [p, q]区间的。即 中节点 > p && 中节点 < q 或者 中节点 > q && 中节点 < p

那么只要从上到下去遍历,遇到 当前节点是数值在[p, q]区间中则一定可以说明该节点就是pq的公共祖先。 那问题来了,一定是最近公共祖先吗?

如图,我们从根节点搜索,第一次遇到 当前节点是数值在[q, p]区间中,即 节点5,此时可以说明 qp 一定分别存在于 节点 5的左子树,和右子树中。

在这里插入图片描述

此时节点5是不是最近公共祖先? 如果 从节点5继续向左遍历,那么将错过成为p的祖先, 如果从节点5继续向右遍历则错过成为q的祖先。

所以当我们从上向下去递归遍历,第一次遇到 当前节点是数值在[q, p]区间中,那么当前就是 qp的最近公共祖先。

理解这一点,本题就很好解了。

而递归遍历顺序,本题就不涉及到 前中后序了(这里没有中节点的处理逻辑,遍历顺序无所谓了)。

如图所示:p为节点6q为节点9

在这里插入图片描述

可以看出直接按照指定的方向,就可以找到节点8,为最近公共祖先,而且不需要遍历整棵树,找到结果直接返回!

递归法

递归三部曲如下:

1.确定递归函数返回值以及参数
参数就是当前节点,以及两个结点 pq。返回值是要返回最近公共祖先,所以是*TreeNode 。和题目所给函数一致,所以不用额外定义辅助函数了。

代码如下:

func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode {}

2.确定终止条件
遇到空返回就可以了,此外如果找到p或者q了也可以返回,代码如下:

if root == nil {
	return nil
}
if root == p || root == q {
    return root
}

其实都不需要遇到空这个终止条件,因为题目中说了pq 为不同节点且均存在于给定的二叉搜索树中。也就是说一定会找到公共祖先的,所以并不存在遇到空的情况。

3.确定单层递归的逻辑
在遍历二叉搜索树的时候就是寻找区间(p.Val, q.Val)(注意这里是左开右开)

那么如果 root.Val 大于 p.Val,同时 root.Val 大于q.Val,那么就应该向左遍历(说明目标区间在左子树上)。

如果 root.Val 小于 p.Val,同时 root.Val 小于 q.Val,那么就应该向右遍历(目标区间在右子树)。

需要注意的是此时不知道pq谁大,所以两个都要判断

剩下的情况,就是当前节点在区间中,那么当前节点就是最近公共祖先了,直接返回当前节点。

代码如下:

if root.Val > p.Val && root.Val > q.Val { // 公共祖先在左子树
	 return lowestCommonAncestor(root.Left,p,q)
 }
 if root.Val < p.Val && root.Val < q.Val { // 公共祖先在右子树
     return lowestCommonAncestor(root.Right,p,q)
 }
 return root // 当前节点就是公共祖先

细心的同学会发现,在这里调用递归函数的地方,把递归函数的返回值继续直接return了。

在二叉树:236. 二叉树的最近公共祖先中,如果递归函数有返回值,如何区分要搜索一条边,还是搜索整个树。

搜索一条边的写法:

if (递归函数(root.Left)) {return XX}
if (递归函数(root.Right)) {return XX}

搜索整个树写法:

left = 递归函数(root.Left)  // 左
right = 递归函数(root.Right) // 右
left与right的逻辑处理        // 中 

本题就是标准的搜索一条边的写法,遇到递归函数的返回值,如果不为空,可以立刻返回,不需要再去关心右子树的情况了。

那么整体递归Go代码如下:

/**
 * Definition for a binary tree node.
 * type TreeNode struct {
 *     Val   int
 *     Left  *TreeNode
 *     Right *TreeNode
 * }
 */

func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode {
	if root == nil {
        return nil
    }
    if root == p || root == q {
        return root
    }
    if root.Val > p.Val && root.Val > q.Val {
        return lowestCommonAncestor(root.Left,p,q)
    }
    if root.Val < p.Val && root.Val < q.Val {
        return lowestCommonAncestor(root.Right,p,q)
    }
    return root
}

在这里插入图片描述

迭代法

利用其有序性,迭代的方式还是比较简单的,解题思路在递归中已经分析了。

迭代代码如下:

func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode {
    for root != nil {
        if root.Val > p.Val && root.Val > q.Val {
            root = root.Left
        } else if root.Val < p.Val && root.Val < q.Val {
            root = root.Right
        } else {
            return root
        }
    }
    return nil
}

灵魂拷问:是不是又被简单的迭代法感动到痛哭流涕?

总结

对于二叉搜索树的最近祖先问题,其实要比普通二叉树公共祖先问题简单的多。

不用使用回溯,二叉搜索树自带方向性,可以方便的从上向下查找目标区间,遇到目标区间内的节点,直接返回。

最后给出了对应的迭代法,二叉搜索树的迭代法甚至比递归更容易理解,也是因为其有序性(自带方向性),按照目标区间找就行了。

二叉搜索树(Binary Search Tree,简称BST)是一种特殊的二叉树,它的每个节点的值都大于其左子树中的所有节点的值,且小于其右子树中的所有节点的值。最近公共祖先(Lowest Common Ancestor,简称LCA)是指在二叉树中,两个节点p和q的最近公共祖先节点。 对于给定的二叉搜索树,我们可以通过比较节点的值来确定最近公共祖先节点。具体步骤如下: 1. 从根节点开始遍历二叉搜索树。 2. 如果当前节点的值大于p和q的值,说明p和q都在当前节点的左子树中,因此继续遍历当前节点的左子树。 3. 如果当前节点的值小于p和q的值,说明p和q都在当前节点的右子树中,因此继续遍历当前节点的右子树。 4. 如果当前节点的值介于p和q的值之间,说明当前节点就是最近公共祖先节点。 以下是一个示例代码,演示了如何找到二叉搜索树最近公共祖先节点: ```python class TreeNode: def __init__(self, val=0, left=None, right=None): self.val = val self.left = left self.right = right def lowestCommonAncestor(root, p, q): if root.val > p.val and root.val > q.val: return lowestCommonAncestor(root.left, p, q) elif root.val < p.val and root.val < q.val: return lowestCommonAncestor(root.right, p, q) else: return root # 创建一个二叉搜索树 root = TreeNode(6) root.left = TreeNode(2) root.right = TreeNode(8) root.left.left = TreeNode(0) root.left.right = TreeNode(4) root.right.left = TreeNode(7) root.right.right = TreeNode(9) root.left.right.left = TreeNode(3) root.left.right.right = TreeNode(5) # 找到节点3和节点5的最近公共祖先 p = TreeNode(3) q = TreeNode(5) lca = lowestCommonAncestor(root, p, q) print("最近公共祖先节点的值为:", lca.val) # 输出:2 ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值