算法思想一:递归
解题思路:
若 root 是 o1,o2 的 最近公共祖先 ,则只可能为以下情况之一:
- o1 和 o2 在 root 的子树中,且分列 root 的 异侧(即分别在左、右子树中);
- o1 = root ,且 o1 在 root 的左或右子树中;
- o2 = root,且 o2 在 root 的左或右子树中
考虑通过递归对二叉树进行先序遍历,当遇到节点 o1 或 o2 时返回。从底至顶回溯,当节点 o1, o2 在节点 root 的异侧时,节点 root 即为最近公共祖先,则向上返回 root
算法流程:
终止条件:
1、当越过叶节点,则直接返回 null ;
2、当 root 等于 o1,o2 ,则直接返回 root ;
递推工作:
1、开启递归左子节点,返回值记为 left ;
2、开启递归右子节点,返回值记为 right ;
3、返回值: 根据 left 和 right ,可展开为四种情况;
1、当 left 和 right 同时为空 :说明 root 的左 / 右子树中都不包含 o1,o2 ,返回 null ;
2、当 left 和 right 同时不为空 :说明 o1,o2 分列在 root 的 异侧 (分别在 左 / 右子树),因此 root为最近公共祖先,返回 root ;
3、 当 left 为空 ,right 不为空 :o1,o2 都不在 root 的左子树中,直接返回 right 。具体可分为两种情况:
1、 o1,o2 其中一个在 root 的 右子树 中,此时 right 指向 o1(假设为 o1 );
2、 o1,o2 两节点都在 root 的 右子树 中,此时的 right 指向 最近公共祖先节点 ;
4、当 left 不为空 , right 为空 ,返回 root
图解:
代码展示:
Python版本
class Solution:
def lowestCommonAncestor(self , root , o1 , o2 ):
# write code here
return self.dfs(root, o1, o2).val
def dfs(self, root, o1, o2):
if not root:
return root
# 特殊情况,如果o1, o2有一个是根节点,则直接返回根节点
if root.val == o1 or root.val == o2:
return root
# 从根节点的左子树查找最近公共祖先结点
left = self.dfs(root.left, o1, o2)
# 从根节点的右子树查找最近公共祖先结点
right = self.dfs(root.right, o1, o2)
# 如果左子树递归结果为空,则返回右子树递归结果
if not left: return right
# 反之亦然
if not right: return left
# o1, o2 分别在左右子树中,公共祖先是根节点
return root 复杂度分析
时间复杂度 : 其中 N 为二叉树节点数;最差情况下,需要递归遍历树的所有节点。
空间复杂度 : 最差情况下,递归深度达到 N ,系统使用
大小的额外空间
算法思想二:哈希表存储父节点
解题思路:
可以用哈希表存储所有节点的父节点,然后我们就可以利用节点的父节点信息从 o1 结点开始不断往上跳,并记录已经访问过的节点,再从 o2 节点开始不断往上跳,如果碰到已经访问过的节点,那么这个节点就是要找的最近公共祖先。
算法流程:
1、从根节点开始遍历整棵二叉树,用哈希表记录每个节点的父节点指针。
2、从 o1 节点开始不断往它的祖先移动,并用数据结构记录已经访问过的祖先节点。
3、同样,我们再从 o2 节点开始不断往它的祖先移动,如果有祖先已经被访问过,即意味着这是 o1 和 o2 的深度最深的公共祖先
代码展示:
JAVA版本
import java.util.*;
/*
* public class TreeNode {
* int val = 0;
* TreeNode left = null;
* TreeNode right = null;
* }
*/
public class Solution {
/**
*
* @param root TreeNode类
* @param o1 int整型
* @param o2 int整型
* @return int整型
*/
// 结点祖先集合
Map parent = new HashMap();
// 访问结点集合
Set visited = new HashSet();
public void dfs(TreeNode root) {
// 循环遍历左子树 添加祖先集合
if (root.left != null) {
parent.put(root.left.val, root.val);
dfs(root.left);
}
// 循环遍历右子树 添加祖先集合
if (root.right != null) {
parent.put(root.right.val, root.val);
dfs(root.right);
}
}
public int lowestCommonAncestor (TreeNode root, int o1, int o2) {
// write code here
// 深度遍历添加结点及祖先集合
dfs(root);
// 遍历存储 o1 的所有祖先
while (parent.get(o1) != null) {
visited.add(o1);
o1 = parent.get(o1);
}
// 如果 o2 是 o1 的祖先路径上的某点,直接返回 o2 即为祖先
while (parent.get(o2) != null) {
if (visited.contains(o2)) {
return o2;
}
// 添加 o2 的所有祖先
o2 = parent.get(o2);
}
return root.val;
}
} 复杂度分析
时间复杂度:,其中 N 是二叉树的节点数。二叉树的所有节点有且只会被访问一次,从 o1 和 o2 节点往上跳经过的祖先节点个数不会超过 N,因此总的时间复杂度为
。
空间复杂度:,其中 N 是二叉树的节点数。递归调用的栈深度取决于二叉树的高度,二叉树最坏情况下为一条链,此时高度为 N,因此空间复杂度为
,哈希表存储每个节点的父节点也需要
的空间复杂度,因此最后总的空间复杂度为
。



京公网安备 11010502036488号