LeetCode 剑指 Offer 68 - I. 二叉搜索树的最近公共祖先
题目描述


题意分析
在节点值互不相同的二叉搜索树中,找到同时包含
p、q且深度最大的祖先节点。题目保证两个目标都存在,并允许节点是自己的祖先,所以答案也可能就是p或q。
解法:利用 BST 性质
核心思路
[!blue]
二叉搜索树的左子树中所有值都小于根,右子树中所有值都大于根,因此比较两个目标与当前节点的值,就能判断它们是否仍在同一侧,无需搜索两条分支。
从根开始,始终保持“当前子树包含两个目标”。若
p、q的值都小于当前值,两个目标都在左子树,左子树内部已经存在公共祖先,答案不必停留在当前节点,继续向左找更深的位置。都大于当前值时同理向右。这样的移动保留两个目标,也保留它们的最近公共祖先。当两个目标分居两侧时,任何更深的节点都只属于其中一侧,不可能再同时成为两者祖先,因此当前节点就是最近公共祖先。另一种停止情况是当前值等于某个目标:当前子树仍包含另一目标,所以当前节点就是它的祖先,而当前节点的后代不可能成为当前节点自己的祖先,答案同样已经确定。
因而沿一条路径不断下降,第一次不能继续向同一侧移动的位置就是答案。每次下降一层,最多经过树高个节点;在两个目标都存在的保证下,会在走到空节点之前找到答案。
解题步骤
- 令
cur指向根节点。- 若两个目标值都小于
cur.val,令cur = cur.left。- 若两个目标值都大于
cur.val,令cur = cur.right。- 否则返回
cur,它是分叉点或其中一个目标本身。两个目标不需要预先按值排序。
代码实现
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
TreeNode cur = root;
while (cur != null) {
// 只有两个目标都在同侧,才能继续向该侧下降。
if (p.val < cur.val && q.val < cur.val) {
cur = cur.left;
} else if (p.val > cur.val && q.val > cur.val) {
cur = cur.right;
} else {
// 目标分居两侧或命中当前节点,此处就是最近公共祖先。
return cur;
}
}
return null;
}
}
func lowestCommonAncestor(root *TreeNode, p *TreeNode, q *TreeNode) *TreeNode {
cur := root
for cur != nil {
// 只有两个目标都在同侧,才能继续向该侧下降。
if p.Val < cur.Val && q.Val < cur.Val {
cur = cur.Left
} else if p.Val > cur.Val && q.Val > cur.Val {
cur = cur.Right
} else {
// 目标分居两侧或命中当前节点,此处就是最近公共祖先。
return cur
}
}
return nil
}
复杂度分析
- 时间复杂度:$O(h)$,其中
h为树高,每层只访问一个节点。平衡时为 $O(\log n)$,退化成链时为 $O(n)$。- 空间复杂度:$O(1)$,迭代只保存当前节点。
关键点总结
[!green]
- 每次移动后,当前子树仍同时包含两个目标。
- 同侧就能继续找更深的公共祖先,分叉或命中目标时停止。
- 严格不等号保留了“目标本身也是祖先”的情况。
易错点总结
[!yellow]
- 同侧判断必须用“且”;只要一个目标更小就向左,会丢掉另一侧的目标。
- 使用小于等于或大于等于继续下降,会越过答案恰好是目标的情况。
- 不能假定
p.val < q.val,代码对两个目标采用对称判断。- 此方法依赖搜索树的有序性,不能直接用于普通二叉树。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 236. 二叉树的最近公共祖先 | 中等 | 普通树不能按值判断两个目标在哪一侧,本题可利用BST性质沿单一路径下降。 |
| 700. 二叉搜索树中的搜索 | 简单 | 同样根据目标与当前值的关系选择子树,本题在两个目标分居两侧时停止。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!