注:本文的代码实现使用的是 JS(JavaScript),为前端中想使用JS练习算法和数据结构的小伙伴提供解题思路。(
描述
给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。
最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 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 为不同节点且均存在于给定的二叉搜索树中。
解题思路
1. 两次遍历法
我们可以两个数组p_path
和q_path
分别记录从根节点到p
和q
的路径,然后从两个路径中找到最后一个相同的节点即可。因此,只要找出最大的编号i
,其满足:
p
_
p
a
t
h
[
i
]
=
q
_
p
a
t
h
[
i
]
p\_path[i] = q\_path[i]
p_path[i]=q_path[i]
由于题目给定的是搜索二叉树,可以利用其性质,优化搜索过程。
var lowestCommonAncestor = function(root, p, q) {
// 查找路径函数,root是原始的树,node是需要查找的节点
let find = (root, node) => {
// 定义路径数组
let path = []
while(root){
path.push(root)
// 根节点的值小于查找节点的值,则遍历右子树
if(root.val < node.val) root = root.right
// 根节点的值大于查找节点的值,则遍历左子树
else if(root.val > node.val) root = root.left
// 找到需要查找的节点,返回路径
else return path
}
}
let p_path = find(root, p)
let q_path = find(root, q)
// 定义祖先变量
let ancestor = null
// 只需要在最短的路径中查找即可
let len = Math.min(p_path.length, q_path.length)
// 比较两个路径,若节点相同,则将 ancestor 指向该节点
for(let i = 0; i < len; i++)
// 只要节点相同,ancestor 就会更新,因此 ancestor 保存的是最后一次相同的节点
if(p_path[i] === q_path[i]) ancestor = p_path[i]
return ancestor
};
2. 一次遍历法
我们还可以简化上面的方法。
- 若节点
p
和q
的值均大于当前根节点的值,说明节点p
和q
的祖先一定在当前节点的右子树上 - 若节点
p
和q
的值均小于当前根节点的值,说明节点p
和q
的祖先一定在当前节点的左子树上 - 如果当前节点的值不满足上述两条要求,那么说明当前节点就是「分岔点」,即最近公共祖先
var lowestCommonAncestor = function(root, p, q) {
while(true){
if(root.val < p.val && root.val < q.val) root = root.right
else if(root.val > p.val && root.val > q.val) root = root.left
else return root
}
};