Problem overview
The function lowestCommonAncestor receives the root of a binary search tree (BST) and two values, p and q, that are both in the tree. It returns the value of their lowest common ancestor: the deepest node that has both of them in its subtree. A node counts as its own ancestor, and p and q may be equal. The tree has 1 to 1,000 nodes with distinct values.
In the tree with root 6, children 2 and 8, then 0, 4, 7, 9 below, and 3 and 5 below 4:
- for 2 and 8 the answer is 6, because they are in different subtrees of 6;
- for 2 and 4 the answer is 2, because 4 is in the subtree of 2 and 2 counts as its own ancestor;
- for 5 and 3 the answer is 4, which has them as its two children.
Approach: let the values choose the direction
In a plain binary tree you would have to search both subtrees. A BST gives more information: everything smaller than a node's value is in its left subtree, and everything larger is in its right subtree. So at any node you can tell where p and q are without looking.
Start at the root, which is above every node and so is always a common ancestor. Then ask where the two values are relative to the current node:
- both smaller: both lie in the left subtree, so the lowest common ancestor is there too, and you can move left;
- both larger: move right;
- otherwise, they are on different sides, or one of them is the current node's value. Going down either way would lose one of them, so the current node is the answer.
Each pass goes down one level, so the walk takes at most as many passes as the tree is tall. That is the O(h) time of the follow-up, with no recursion and no stack, so O(1) extra space.
Step by step
- Declare
nodeand set it toroot. It marks the node the walk is at. - Loop while
node != nil. In Go, aforwith only a condition is the while loop. Each pass looks at one node and either returns or moves one level down. - If
pandqare both smaller thannode.Val, joined with&&, setnodetonode.Left. - Else if both are larger, set
nodetonode.Right. - Otherwise return
node.Val. The values split here, or one of them equals the node's value, and in both cases this node is the deepest one with both below it. - Return -1 after the loop. Because both values are in the tree, the loop always returns first, but Go requires a function that returns an
intto end with areturn.
The solution in Go
func lowestCommonAncestor(root *TreeNode, p, q int) int {
node := root
for node != nil {
if p < node.Val && q < node.Val {
node = node.Left
} else if p > node.Val && q > node.Val {
node = node.Right
} else {
return node.Val
}
}
return -1
}
Go details worth a closer look:
- Pointers walk the tree.
nodeis a*TreeNode. Assigningnode = node.Leftmoves the pointer; it does not copy or change the tree. - Values, not nodes. The function receives
pandqasintvalues, not as node pointers, so the comparisons usenode.Valdirectly. else ifchains. Go'sifneeds no parentheses around the condition, but the braces are required, andelsemust sit on the same line as the closing brace of the previous block.- The final
return -1. The compiler cannot know that the loop always returns early, so without a final return the function is rejected as missing one. The value -1 is a placeholder that does not appear in a valid run. - Nil children. If the walk reached a missing child,
nodewould becomeniland the loop would stop. With valid input that cannot happen.
Complexity
Time: O(h), where h is the height of the tree. The walk goes down one level per pass and never goes back. For a balanced tree that is about log n steps, and for a tree that is a single chain it is n.
Space: O(1). Only the node pointer is kept, as the loop replaces recursion.
Edge cases and common mistakes
- One value is an ancestor of the other. For 2 and 4 the walk reaches the node 2: 4 is not smaller than 2, so neither "both smaller" nor "both larger" holds, and 2 is returned. No special case is needed.
- Equal values. With
p == q == 7the walk goes right from 6, reaches 8, goes left to 7, and stops there because neither strict comparison holds. - Using
<=or>=. Those would keep walking past a node whose value equalsporqand give a lower node than the correct answer. - The order of
pandq. It does not matter:(8, 2)gives 6 just like(2, 8). - Treating it as a general binary tree. A solution that searches both children works, but it is
O(n); it also ignores the BST order that this lesson uses. - The root is the answer. For 6 and 3 the first node is 6, equal to
p, so it is returned at once.
Testing
On the tree from the overview:
p = 2,q = 8gives 6.p = 2,q = 4gives 2.p = 5,q = 3gives 4.p = 8,q = 2gives 6.p = 7,q = 7gives 7.p = 0,q = 5gives 2.p = 6,q = 3gives 6.
