原始题目:剑指 Offer 68 - II. 二叉树的最近公共祖先 (opens new window)
# 方法一:哈希表
如果能知道每个节点的父节点,那么就可以通过 , 向上追溯找到最近的共同的父节点。因为题目中的二叉树并没有 指针,所以需要使用哈希表来映射子节点到父节点的关系。
拿到子节点和父节点的映射关系后,从 开始往上访问父节点,并记录起来。然后再从 开始往上遍历父节点,如果碰到已访问的节点,那么这个节点就是最近公共父节点。
代码:
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
Map<TreeNode, TreeNode> parentMap = new HashMap<>();
Set<TreeNode> visited = new HashSet<>();
getParentMap(parentMap, root, null);
while(p != null) {
visited.add(p);
p = parentMap.get(p);
}
while(q != null) {
if(visited.contains(q)) {
return q;
}
q = parentMap.get(q);
}
return null;
}
private void getParentMap(Map<TreeNode, TreeNode> map, TreeNode node, TreeNode parent) {
if (node == null) {
return;
}
map.put(node, parent);
getParentMap(map, node.left, node);
getParentMap(map, node.right, node);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
复杂度分析
- 时间复杂度: 为二叉树的节点个数。最差情况下,树退化成 “^” 型链表 ,最近公共父节点为根节点。
- 空间复杂度:最差情况下,需要存储所有的树节点。
# 方法二:深度优先搜索
后序遍历二叉树,定义 表示 x 节点的子树是否包含 p 或者 q。如果包含为 true,否则为 false。那么最近的公共祖先一定满足如下条件:
和 分别表示 x 的左右子节点。这个条件表示
- 如果 或者 分别散布在 x 的左节点或者子节点,那么 就是 , 的最近公共父节点。
- 比如 在 的左子树, 在 的右子树。
- 或者 就是 节点本身,那么就看另一个节点是否在 x 的子树中
- 比如 本身就是 节点了,那么就看 是否在 的子树中。
因为是自底向上的遍历,所以找到的公共祖先的深度一定是最大的。
代码:
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
findSon(root, p, q);
return this.ans;
}
/**
* 判断 root 中是否包含 p 或者 q
*/
private boolean findSon(TreeNode root, TreeNode p, TreeNode q) {
if(root == null) {
return false;
}
boolean inLeft = findSon(root.left, p, q);
boolean inRight = findSon(root.right, p, q);
if((inLeft && inRight) ||
((root.val == p.val || root.val == q.val) && (inLeft || inRight))){
this.ans = root;
}
return inLeft || inRight || (root.val == p.val) || (root.val == q.val);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
复杂度分析
- 时间复杂度:其中, 为二叉树的节点数。二叉树的节点有且只会被访问一次,因此时间复杂度为 。
- 空间复杂度:最差情况下,树退化成链表,需要 递归栈空间。