Binary Search Trees · 15 of 22
Find the median of BST in O(n) time and O(1) space
Find the middle number of a sorted tree without any list or stack, by tying temporary ropes that let you climb back up and walking the tree in order twice.
- Simple way
- time O(n)
room O(n) - Clever way
- time O(n)
room O(1)
The problem, simply
You get a binary search tree (BST) with n numbers. Give back the median: the middle number when they are sorted. If n is even there are two middle numbers, and the median is their average.
The hard part is the rule: use only O(1) extra room. No list of numbers, no stack, and no recursion (recursion secretly uses a stack as tall as the tree).
A story to remember it
Picture it
Five numbers. Sorted, they are 1, 3, 4, 6, 8, so the median is the 3rd one, 4. Read the tree left part, room, right part to get that order.
- answer
Examples
Odd count
- In
[6, 3, 8, 1, 4]- Out
4
Sorted: 1, 3, 4, 6, 8. The 3rd of 5 is 4.
Even count
- In
[6, 3, 8, 1, 4, 7]- Out
5.0
Sorted: 1, 3, 4, 6, 7, 8. The middle two are 4 and 6. Their average is 5.
A stick
- In
[1, null, 2, null, 3]- Out
2
Sorted: 1, 2, 3. The middle is 2.
One number
- In
[7]- Out
7
The only number is the middle.
Way 1: the simple way
Write the sorted order into a list and look at the middle.
- Visit left part, room, right part, adding each number to a list.
- If
nis odd, returnlist[n / 2]. If even, return the average oflist[n/2 - 1]andlist[n/2].
import java.util.*;
class Solution {
public double findMedian(TreeNode root) {
List<Integer> sorted = new ArrayList<>();
inorder(root, sorted);
int n = sorted.size();
if (n == 0) return 0;
if (n % 2 == 1) return sorted.get(n / 2);
return (sorted.get(n / 2 - 1) + sorted.get(n / 2)) / 2.0;
}
private void inorder(TreeNode node, List<Integer> sorted) {
if (node == null) return;
inorder(node.left, sorted);
sorted.add(node.val);
inorder(node.right, sorted);
}
}How long it takes: every spot once, O(n). Extra room: the list, O(n), plus O(h) for the recursion. This breaks the O(1) rule.
Way 2: the clever way
Walk in sorted order with ropes instead of a stack. This is the Morris walk. At each spot curr:
- If
currhas no left part, visit it and go right. (Going right may follow a rope back up.) - Otherwise find the last spot of its left part: go left once, then right until the next step is
nullor iscurr. - If that spot's
rightisnull, tie a rope: point it atcurr, then go left. - If that spot's
rightis alreadycurr, you came back by the rope. Untie it, visitcurr, and go right.
Do the walk twice. The first time, only count, to learn n. The second time, write down the number at position (n + 1) / 2 and, for even n, also at n / 2 + 1. Let the second walk finish so every rope gets untied.
The second walk: ropes up, count to 3, ropes down
01 / 08
The first walk counted n = 5. So the median is the 3rd number in sorted order. Now walk again. curr starts at the top, 6.
- n
- 5
- count
- 0
- want #
- 3
- looking now
- checking
- done
- answer
- path
class Solution {
public double findMedian(TreeNode root) {
if (root == null) return 0;
int n = walk(root, 0, 0, new int[2]); // first walk: just count
int a = (n + 1) / 2, b = n / 2 + 1; // middle positions, from 1 (same if n is odd)
int[] got = new int[2];
walk(root, a, b, got); // second walk: pick them up
return (got[0] + got[1]) / 2.0;
}
// Morris walk in sorted order. Saves the a-th and b-th numbers. Returns the count.
private int walk(TreeNode root, int a, int b, int[] got) {
int count = 0;
TreeNode curr = root;
while (curr != null) {
if (curr.left != null) {
TreeNode last = curr.left; // last spot of the left part
while (last.right != null && last.right != curr) last = last.right;
if (last.right == null) { // first time: tie a rope
last.right = curr;
curr = curr.left;
continue;
}
last.right = null; // back by the rope: untie
}
count++; // visit curr
if (count == a) got[0] = curr.val;
if (count == b) got[1] = curr.val;
curr = curr.right;
}
return count;
}
}How long it takes: each walk is O(n). Finding "the last spot of the left part" looks slow, but each branch is walked at most a few times in total across the whole walk. Two walks, still O(n). Extra room: a few fingers, O(1).
Watch out for
- Integer division for even
n:(a + b) / 2drops the half. Divide by2.0, and be careful thata + bcan overflow for huge values (uselong). - Off by one positions. With counting from 1, the middle is
(n + 1) / 2for oddn, andn / 2andn / 2 + 1for evenn. - Recursion or a stack is not
O(1)room. The interviewer is asking for Morris on purpose.
What interviewers ask next
- Why does Morris not lose any spot? The left part's last spot never has a right child of its own, so its empty
rightarrow is free to borrow, and it is given back on the way up. - Can you find the median in one walk? Only if you already know
n, for example if the tree stores its size. Otherwise you need the count first. - Median of a stream of numbers (LeetCode 295). Keep two heaps: a max heap for the smaller half and a min heap for the bigger half.
O(log n)per number. - The
k-th smallest inO(1)room. Same Morris walk, stop counting atk, and still finish the walk to untie ropes.
Remember this
Grown-up words: exploring left, room, right is an inorder traversal, the rope is a thread (a temporary right pointer to the inorder successor), the whole trick is a Morris traversal, and the last spot of the left part is the inorder predecessor.