Cracking Coding Interview - 4.8 First Common Ancestor

First Common Ancestor: Design an algorithm and write code to find the first common ancestor of two nodes in a binary tree. Avoid storing additional nodes in a data structure. NOTE: This is not necessarily a binary search tree.

Hints: #10, #16, #28, #36, #46, #70, #80, #96

解法1

二叉树任意两节点的第一个共同祖先,假设每个node都有一个parent指针。

暴力的做法是:

  • 从node A出发往下递归找node B,如果找到了则node A就是node B的祖先
  • 没找到,从node A的parent出发往下滴贵找node B,如果找到了则node A的parent是node B的共同祖先
  • 没找到,从node A的parent的parent,。。。
  • 。。。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public Node findAncestor(Node parent, Node b) {
  if (parent == null) {
    // 说明这两个不在一颗树里
    return null;
  }
  if (contains(parent, b)) {
    return parent;
  }
  return findAncestor(parent.parent, b);
}

// 判断这个node及其所有子孙是否含有target node。
public boolean contains(Node node, Node target) {
  if (node == null) {
    return false;
  }
  if (node == target) {
    return true;
  }
  if (node.left == target || node.right == target) {
    return true;
  }
  return contains(node.left, target) || contains(node.right, target);
}

时间复杂度:O(n^2),试想a、b两个节点分别在一颗树的最左和最右两端。

解法2

解法1中有一些重复计算,所以它时O(n^2),比如下面在2这个子树没有找到的情况下,上升到1时,在从1往下找的时候不需要再到2里找一遍,而应该只找3。

1
2
3
4
5
    1
   / \
  2   3
 / \
4   5

代码:

 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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
findAncestor(a, b, null);

public Node findAncestor(Node parent, Node b, Exclusion exclusion) {
  if (parent == null) {
    // 说明这两个不在一颗树里
    return null;
  }
  if (parent == b) {
    return parent;
  }
  if (exclusion == LEFT) {
    if (contains(parent.right, b)) {
      return parent;
    }
  } else if (exclusion == RIGHT) {
    if (contains(parent.left, b)) {
      return parent;
    }
  } else {
    if (contains(parent, b)) {
      return parent;
    }
  }
  if (parent.parent != null && parent == parent.parent.left) {
    return findAncestor(parent.parent, b, Exclusion.LEFT);
  }
  if (parent.parent != null && parent == parent.parent.right) {
    return findAncestor(parent.parent, b, Exclusion.RIGHT);
  }
  return null;
}

// 判断这个node及其所有子孙是否含有target node。
public boolean contains(Node node, Node target) {
  if (node == null) {
    return false;
  }
  if (node == target) {
    return true;
  }
  if (node.left == target || node.right == target) {
    return true;
  }
  return contains(node.left, target) || contains(node.right, target);
}

时间复杂度:O(n)

解法3

如果可以拿到parent,那么这个问题就和2.7-intersection有点像了:从两个节点出发,沿着parent链表一路走到底,找到第一个交叉点,这个交叉点就是共同祖先。

解法4

在没有parent指针的情况下怎么弄?

共同祖先有什么特性:

1
2
3
4
5
     p
    / \
   x   b
  / \
 a   x

p是a、b的第一个共同祖先,a、b分别在p的左右两边

1
2
3
4
5
     a
    /
   x
  / \
 x   b

a就是a、b的第一个共同祖先,a、b在一边。

所以共同祖先p有两个特性:

  1. a、b在p的左右两边
  2. 或者p == a || p == b

我们先从root开始,判断上面的两个特性是否成立,如果不成立,那么就下沉到左边or右边继续这个过程。

 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
26
27
28
public Node findAncestor(Node root, Node a, Node b) {
  if (root == a || root == b) {
    return root;
  }
  boolean aLeft = contains(root.left, a);
  boolean bLeft = contains(root.left, b);
  if (aLeft xor bLeft) {
    return root;
  }
  if (aleft && bLeft) {
    return findAncestor(root.left, a, b);
  }
  return findAncestor(root.right, a, b);
}

// 判断这个node及其所有子孙是否含有target node。
public boolean contains(Node node, Node target) {
  if (node == null) {
    return false;
  }
  if (node == target) {
    return true;
  }
  if (node.left == target || node.right == target) {
    return true;
  }
  return contains(node.left, target) || contains(node.right, target);
}

版权

评论