Binary Search Trees · 21 of 22
Largest BST in a Binary Tree
Find the biggest piece of a messy tree that is a proper sorted tree, by letting every spot report its size, smallest and biggest number up to its parent.
- 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. Some parts of it may follow the binary search tree (BST) rule: everything on the left side of a spot is smaller and everything on the right side is bigger. Find the largest such part and return how many spots it has.
A "part" here means a spot together with everything below it (a subtree). You cannot pick and choose spots.
A story to remember it
Picture it
The whole tree is not a BST, because 20 sits on the right of 30 but is smaller. The part under 60 is a BST with 5 spots: 45, 60, 65, 70, 80.
- answer
- rejected
Examples
BST in one branch
- In
[50, 30, 60, 5, 20, 45, 70, null, null, null, null, null, null, 65, 80]- Out
5
The part under 60 is a BST of 5 spots. The part under 30 is broken by 20.
Small parts only
- In
[10, 5, 15, 1, 8, null, 7]- Out
3
5 with children 1 and 8 is a BST of size 3. 15 with right child 7 is not, because 7 is smaller than 15.
The whole tree
- In
[2, 1, 3]- Out
3
Everything is in order, so the answer is the whole tree.
Nothing bigger than one
- In
[5, 6, 4]- Out
1
The top is broken (6 on the left, 4 on the right). But every single spot is a BST of size 1 on its own.
Empty tree
- In
[]- Out
0
No spots, no BST.
Way 1: the simple way
For each spot, check from scratch whether its part is a BST. If yes, count it. If not, look further down.
- If the part under this spot is a BST (use the "window" check from "Check if a tree is a BST or not"), return its size.
- Otherwise return the bigger answer of the left side and the right side.
class Solution {
public int largestBst(TreeNode root) {
if (root == null) return 0;
if (isBST(root, Long.MIN_VALUE, Long.MAX_VALUE)) return size(root);
return Math.max(largestBst(root.left), largestBst(root.right));
}
private boolean isBST(TreeNode n, long low, long high) {
if (n == null) return true;
if (n.val <= low || n.val >= high) return false;
return isBST(n.left, low, n.val) && isBST(n.right, n.val, high);
}
private int size(TreeNode n) {
return n == null ? 0 : 1 + size(n.left) + size(n.right);
}
}How long it takes: a check at a spot costs the size of its part. In a stick shaped tree that is broken at the bottom, the parts have sizes n, n - 1, ..., 1, so O(n^2). Extra room: the recursion, O(h).
Way 2: the clever way
Work from the bottom up, and let every spot send one report to its parent: is my part a BST, how big is it, and what are its smallest and biggest numbers.
- An empty spot reports: "BST, size 0, smallest +infinity, biggest -infinity". These fake values make every comparison above it pass.
- A spot first gets the reports from its left and right side.
- Its part is a BST if both sides are BSTs, and
left.biggest < me < right.smallest. - If yes, report size
1 + left.size + right.size, smallestmin(me, left.smallest), biggestmax(me, right.biggest), and update the best answer. - If no, report "not a BST". The parent will then fail too.
Reports travel up from the leaves
01 / 07
Start at the bottom left. 5 and 20 are leaves. Each one alone is a BST of size 1, with smallest and biggest equal to itself.
- best
- 1
- checking
- done
- answer
- rejected
- ignored
class Solution {
// the report each spot sends up
static class Info {
boolean bst;
int size;
long min, max;
Info(boolean bst, int size, long min, long max) {
this.bst = bst; this.size = size; this.min = min; this.max = max;
}
}
private int best;
public int largestBst(TreeNode root) {
best = 0;
report(root);
return best;
}
private Info report(TreeNode node) {
if (node == null) return new Info(true, 0, Long.MAX_VALUE, Long.MIN_VALUE);
Info l = report(node.left);
Info r = report(node.right);
if (l.bst && r.bst && l.max < node.val && node.val < r.min) {
int size = 1 + l.size + r.size;
best = Math.max(best, size);
return new Info(true, size, Math.min(node.val, l.min), Math.max(node.val, r.max));
}
return new Info(false, 0, 0, 0); // broken: the parent will be broken too
}
}How long it takes: every spot makes one report from two smaller reports, O(n). Extra room: the recursion, O(h).
Watch out for
- The empty report must use "smallest = +infinity, biggest = -infinity", so a leaf passes the test. Using
longavoids trouble when a real value isInteger.MIN_VALUEorInteger.MAX_VALUE. - For the smallest of a valid part, use
min(me, left.min), notleft.minalone. With an empty left side,left.minis +infinity. - When a part is broken, its parent is broken too. You do not need to pass sizes up, because
bestalready remembers them.
What interviewers ask next
- Return the largest BST's top spot, not its size. Store the spot together with
best. - Largest BST by sum of values instead of size (LeetCode 1373). Carry
sumin the report instead ofsize, and remember the biggest sum, which may be 0 if all values are negative. - Count how many parts are BSTs. Add 1 each time a report says "BST".
- Why not top down? A top down check repeats work for every spot. Bottom up, each spot reuses its children's answers. This "collect info from children, then decide" pattern solves many tree problems: diameter, balanced check, maximum path sum.
Remember this
Grown-up words: a team under a coach is a subtree, sending reports up from the bottom is a postorder traversal, the report is a tuple of return values, and reusing children's answers is the "tree DP" pattern.