[路飞]_leetcode刷题_剑指 Offer 68 - I. 二叉搜索树的最近公共祖先

111 阅读2分钟

题目

剑指 Offer 68 - I. 二叉搜索树的最近公共祖先

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

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

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

image.png

示例 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的关系,无非三种

  1. root.val>p.val && root.val>q.val,这种情况说明p和q都在root的左子树
  2. root.val<p.val && root.val<q.val,这种情况说明p和q都在root的右子树
  3. 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;
};