题目
剑指 Offer 68 - I. 二叉搜索树的最近公共祖先
给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 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, 因为根据定义最近公共祖先节点可以为节点本身。
思路1:
题目说的是二叉搜索树,那么我们根据他的特性可以很快的找到根节点到对应节点到路径。
- 目标节点的值小于root,那么root往左走,目标节点大于root,那么往右走,每走一步,将节点放入路径数组中,直到找到对应的节点。
- 然后再遍历两个路径数组,找到第一个分叉的位置,那么这个分叉点即为最近公共祖先
代码如下:
/**
* @param {TreeNode} root
* @param {TreeNode} p
* @param {TreeNode} q
* @return {TreeNode}
*/
var lowestCommonAncestor = function(root, p, q) {
let path_p = getPath(root,p)
let path_q = getPath(root,q)
let len = Math.min(path_p.length,path_q.length);
for(let i=0;i<len;i++){
if(path_p[i+1] != path_q[i+1]){
return path_p[i];
}
}
};
function getPath(root,node){
let path = [root]
while(root){
if(root.val == node.val){
return path
}
if(root.val > node.val){
root = root.left;
}else{
root = root.right;
}
path.push(root);
}
}
复杂度分析:
时间复杂度:O(n),3次遍历,最坏的情况是O(3n),去掉常数为O(n)
空间复杂度:O(n),最坏的情况是O(2n),去掉常数是O(n)
思路2:
对上面的思路1,可以有以下的优化解
当前节点root和p、q的关系,无非三种
- root.val>p.val && root.val>q.val,这种情况说明p和q都在root的左子树
- root.val<p.val && root.val<q.val,这种情况说明p和q都在root的右子树
- p.val<= root.val <=q.val,这种情况说明p和q分别在root的两边,仔细想想,这种情况,是不是说明root就是他们两个的最近公共祖先,不明白的同学可以找纸画一画看看。
代码如下:
/**
* @param {TreeNode} root
* @param {TreeNode} p
* @param {TreeNode} q
* @return {TreeNode}
*/
var lowestCommonAncestor = function(root, p, q) {
while(true){
if(p.val<root.val && q.val<root.val){
root = root.left;
}else if(p.val>root.val && q.val>root.val){
root = root.right;
}else{
break
}
}
return root;
};
复杂度分析
时间复杂度:O(n),最坏情况二叉树是个链表,而且p、q在最末尾,那么时间复杂度为O(n)
空间复杂度:由于不用存储路径了,空间复杂度直接是常量O(1)
思路3:
对于思路2,可以有更简洁的写法,使用递归去改造一下,原理是一样的。
代码如下:
/**
* @param {TreeNode} root
* @param {TreeNode} p
* @param {TreeNode} q
* @return {TreeNode}
*/
var lowestCommonAncestor = function(root, p, q) {
if (p.val<root.val && q.val<root.val) return lowestCommonAncestor(root.left,p,q);
if (p.val>root.val && q.val>root.val) return lowestCommonAncestor(root.right,p,q);
return root;
};