Binary Search Trees · 5 of 22
Check if a tree is a BST or not
Check that every number in a tree sits inside the window its ancestors allow, so smaller ones are always on the left and bigger ones always on the right.
- Simple way
- time O(n^2)
room O(h) - Clever way
- time O(n)
room O(h)
The problem, simply
You get a binary tree. Say true if it is a binary search tree (BST), and false if not.
A BST has one rule, and it is stronger than it looks: for every spot, every number anywhere on its left side is smaller than it, and every number anywhere on its right side is bigger. Checking only a spot and its two children is not enough.
A story to remember it
Picture it
Left: a real BST. Right: not a BST. 6 is bigger than its parent 3, which looks fine, but 6 lives on the left side of 5, so it must be smaller than 5.
- checking
- done
- rejected
Examples
A real BST
- In
[5, 3, 7, 1, 4, null, 9]- Out
true
Every spot sits inside its window. Read left, me, right: 1, 3, 4, 5, 7, 9, which is sorted.
The sneaky grandchild
- In
[5, 3, 7, 1, 6, null, 9]- Out
false
Each parent and child pair looks fine. But 6 is on the left side of 5 and is bigger than 5.
Equal values
- In
[2, 2, 2]- Out
false
The usual rule says strictly smaller on the left and strictly bigger on the right. Ask the interviewer, but the classic answer is false.
Edge numbers
- In
[2147483647]- Out
true
One spot holding the biggest int. If your window starts at Integer.MAX_VALUE, you wrongly say false. Use long or null for "no limit".
Empty tree
- In
[]- Out
true
An empty tree breaks no rules.
Way 1: the simple way
For every spot, look at its whole left side and find the biggest number there. Look at its whole right side and find the smallest. The spot must sit strictly between them.
- If the biggest number on the left is not smaller than the spot, say
false. - If the smallest number on the right is not bigger than the spot, say
false. - Do the same for every spot below.
class Solution {
public boolean isValidBST(TreeNode root) {
if (root == null) return true;
if (root.left != null && maxOf(root.left) >= root.val) return false;
if (root.right != null && minOf(root.right) <= root.val) return false;
return isValidBST(root.left) && isValidBST(root.right);
}
// the biggest number anywhere in this part of the tree
private int maxOf(TreeNode n) {
int best = n.val;
if (n.left != null) best = Math.max(best, maxOf(n.left));
if (n.right != null) best = Math.max(best, maxOf(n.right));
return best;
}
private int minOf(TreeNode n) {
int best = n.val;
if (n.left != null) best = Math.min(best, minOf(n.left));
if (n.right != null) best = Math.min(best, minOf(n.right));
return best;
}
}How long it takes: every spot scans everything below it. For a stick shaped tree that is n + (n - 1) + ... + 1, so O(n^2). Extra room: the recursion depth, O(h).
Way 2: the clever way
Give every spot its rule card: a window (low, high) that it must sit strictly inside.
- The top gets the window (no limit, no limit).
- When you step to the left child, the window's high end becomes the parent's value. The low end stays.
- When you step to the right child, the window's low end becomes the parent's value. The high end stays.
- If any spot is outside its window, say
false. If you check everyone, saytrue.
The note under each spot in the animation is its window. The pile on the side is the list of helpers still waiting, which is the recursion.
Windows catch the sneaky 6
01 / 06
The top, 5, has no limits. Its window is everything. 5 is fine.
- low
- -inf
- high
- +inf
- looking now
- checking
- done
- rejected
- path
- ignored
class Solution {
public boolean isValidBST(TreeNode root) {
return fits(root, null, null);
}
// low and high are the window; null means "no limit"
private boolean fits(TreeNode node, Integer low, Integer high) {
if (node == null) return true;
if (low != null && node.val <= low) return false;
if (high != null && node.val >= high) return false;
return fits(node.left, low, node.val) // left: high end tightens
&& fits(node.right, node.val, high); // right: low end tightens
}
}How long it takes: every spot is checked once, O(n). Extra room: the recursion depth, O(h). Using null for "no limit" avoids the bug where a real value equals Integer.MIN_VALUE or Integer.MAX_VALUE.
Another way: the sorted walk
Read the tree left side, then the spot, then the right side. In a BST this always gives a strictly growing row. So walk in that order and check each number against the one before it. This version uses a stack instead of recursion, which interviewers like to see.
import java.util.*;
class Solution {
public boolean isValidBST(TreeNode root) {
Deque<TreeNode> stack = new ArrayDeque<>();
TreeNode curr = root;
Integer prev = null;
while (curr != null || !stack.isEmpty()) {
while (curr != null) { // go left as far as you can
stack.push(curr);
curr = curr.left;
}
curr = stack.pop(); // the next number in sorted order
if (prev != null && curr.val <= prev) return false;
prev = curr.val;
curr = curr.right;
}
return true;
}
}Same cost: O(n) time and O(h) room.
Watch out for
- Starting the window at
Integer.MIN_VALUEandInteger.MAX_VALUEwith<=and>=. A tree[2147483647]then fails. UseIntegerwithnull, orlongbounds. - Duplicates: use
<=and>=in the failure checks unless the problem says duplicates are allowed on one side. - Keeping
prevas a field in the sorted walk and forgetting to reset it between test calls.
What interviewers ask next
- Return the size of the largest part of a tree that is a BST. Do it bottom up: each spot returns its size, min, max and whether it is a BST. See "Largest BST in a Binary Tree".
- Two spots of a BST were swapped by mistake. Fix it. In the sorted walk, find the one or two places where the row goes down, and swap the first big one with the last small one.
- Can you do it in
O(1)extra room? Yes, with a Morris walk that temporarily threads each spot's predecessor back to it. StillO(n)time. - Which way stops earlier on a bad tree? Both stop at the first broken spot they meet, but they meet spots in different orders.
Remember this
Grown-up words: the rule card is a pair of bounds (low, high), copying and tightening it on the way down is depth first search with bounds, the waiting helpers are the call stack, the sorted walk is an inorder traversal, and a strictly growing row is a strictly increasing sequence.