1
class TreeNode {
    TreeNode parent;
    TreeNode left;
    TreeNode right;

    // other data fields omitted - not relevant
}

You are given two nodes p, and q, how do you find the lowest common ancestor? (Assume they both belong in a very large tree)

You do NOT have reference to the root of the tree.

What is the most efficient way to do this? So far, the only idea I have was to

(1) pick a node p (doesn't matter which)

(2) search left sub tree of p, if see q, return p

(3) else search right sub tree of p, if see q, return p

(4) else go one level to parent and search the sub-tree that doesn't contain p, if found q, return parent

(5) else, go up one level again, repeat (4) (search the sub tree that doesnt contain this parent)

This seems extremely inefficient. Any better algorithm?

One Two Three
  • 22,327
  • 24
  • 73
  • 114
  • Traverse up from `p` one step at a time (and there is only one parent). At each step, recurse left and right completely, looking for the `q` node. If found, return true otherwise return false. Along the way, as you touch each node, mark it as dirty so that you don't have to repeat the recursion at a higher level. – Tim Biegeleisen Oct 23 '17 at 02:14

1 Answers1

1

Do you have extreme restrictions on the amount of RAM you're allowed to use? If not, I'd propose something like this:

visited_nodes = {}    // something like a HashMap, with O(1) insert and retrieval
node1 = p
node2 = q

while True:
    if node1 == null and node2 == null:    // the nodes don't seem to be in same tree
        return null    // or failure or anything like that

    if node1 is not null:
        if node1 in visited_nodes:    // node1 must be lowest common ancestor
            return node1
        else:
            visited_nodes.insert(node1)    // remember that we've seen this node
            node1 = node1.getParent()

    if node2 is not null:
        if node2 in visited_nodes:    // node2 must be lowest common ancestor
            return node2
        else:
            visited_nodes.insert(node2)    // remember that we've seen this node
            node2 = node2.getParent()

The intuitive idea is as follows. We start at both nodes at the same time. In every iteration of the loop, we take one step up from both of the nodes. Whenever we see a node, we put it in our map (which should have O(1) insertion and retrievals / checking if it's already in there). When we encounter a node that we've already put in the map, that must be our solution.

This code should never run for more than max(d_p, d_q) iterations, where d_p and d_q denote the depth levels in the tree that p and q are at, respectively. This will especially be a large advantage if both nodes happen to be rather close to the root. It also means that the code even works for an infinite tree (whereas your solution would get stuck in an infinite loop).

Dennis Soemers
  • 8,090
  • 2
  • 32
  • 55