Trees · Binary Search Tree Operations

Largest BST Subtree

Determining, bottom-up, whether each subtree is itself a valid BST - and if so, its size and value range - so that a parent can decide in O(1) whether combining its two (already-validated) children with itself still forms a larger valid BST, without ever re-scanning a subtree already proven valid or invalid.

  • 6 min read
  • 9 practice questions
  • 2 graded problems
On this page
  1. Description
  2. The approach
  3. A second flavor: the highest-sum BST instead of the largest
  4. Where you'll see it
  5. Which tool for which job?
  6. Common mistakes
  7. Check yourself
  8. Key takeaways
  9. Quick revise
  10. Practice

Description

A binary tree that isn't a BST can still contain subtrees that are. Checking every subtree from scratch repeats a lot of work. The fix is a bottom-up pass in which each subtree hands its parent a small summary: whether it's a BST, how big it is, and its smallest and largest values. With that, the parent can judge itself in O(1). This skill is about choosing that summary, and the same method answers related questions such as the highest-sum BST.

We'll go from simple to more technical:

  1. The pattern: return (size, min, max) from each subtree, postorder.
  2. A second flavor: rank by sum instead of size.
  3. Where "largest valid region" questions show up.
  4. What breaks it: local-only checks, re-validation, invalid that doesn't propagate, and sentinels.

The approach

Java

int maxSize = 0;

int largestBSTSubtree(TreeNode root) {
    helper(root);
    return maxSize;
}

// returns {size, min, max}; size == -1 means "not a BST"
int[] helper(TreeNode node) {
    if (node == null) return new int[]{0, Integer.MAX_VALUE, Integer.MIN_VALUE};  // empty: a BST that blocks nothing
    int[] left = helper(node.left);
    int[] right = helper(node.right);
    if (left[0] != -1 && right[0] != -1 && node.val > left[2] && node.val < right[1]) {
        int size = left[0] + right[0] + 1;
        maxSize = Math.max(maxSize, size);
        return new int[]{size, Math.min(left[1], node.val), Math.max(right[2], node.val)};
    }
    return new int[]{-1, 0, 0};
}

Python

def largest_bst_subtree(root: TreeNode | None) -> int:
    max_size = 0

    def helper(node: TreeNode | None) -> tuple:
        """(size, min, max); size == -1 means "not a BST"."""
        nonlocal max_size
        if node is None:
            return 0, float("inf"), float("-inf")     # empty: a BST that blocks nothing
        l_size, l_min, l_max = helper(node.left)
        r_size, r_min, r_max = helper(node.right)
        if l_size != -1 and r_size != -1 and l_max < node.val < r_min:
            size = l_size + r_size + 1
            max_size = max(max_size, size)
            return size, min(l_min, node.val), max(r_max, node.val)
        return -1, 0, 0

    helper(root)
    return max_size

A subtree is a BST exactly when both children are BSTs and the node's value lies strictly between the largest value on its left and the smallest on its right. The children's min and max give that in O(1). Comparing only with the direct children isn't enough - a value deep in the left subtree can still be larger than the node.

The empty subtree reports min = Integer.MAX_VALUE and max = Integer.MIN_VALUE, so any real value passes the check against it with no null branches - and Math.min(left[1], node.val) falls back to the node's own value when the left side is empty.

A second flavor: the highest-sum BST instead of the largest

Change what the summary carries and the same pass answers a different question. For "the largest sum of keys in any BST subtree", carry the subtree's sum instead of its size:

Java

long best = Long.MIN_VALUE;    // not 0: with all-negative values, the answer is negative

// returns {isBST, sum, min, max}; bounds in long so no real int value can reach them
long[] summarize(TreeNode node) {
    if (node == null) return new long[]{1, 0, Long.MAX_VALUE, Long.MIN_VALUE};
    long[] l = summarize(node.left), r = summarize(node.right);
    if (l[0] == 1 && r[0] == 1 && l[3] < node.val && node.val < r[2]) {
        long sum = l[1] + r[1] + node.val;
        best = Math.max(best, sum);
        return new long[]{1, sum, Math.min(l[2], node.val), Math.max(r[3], node.val)};
    }
    return new long[]{0, 0, 0, 0};
}

Python

def max_sum_bst(root: TreeNode | None) -> int:
    best = float("-inf")     # not 0: with all-negative values, the answer is negative

    def summarize(node: TreeNode | None) -> tuple:
        """(is_bst, sum, min, max)"""
        nonlocal best
        if node is None:
            return True, 0, float("inf"), float("-inf")
        l_ok, l_sum, l_min, l_max = summarize(node.left)
        r_ok, r_sum, r_min, r_max = summarize(node.right)
        if l_ok and r_ok and l_max < node.val < r_min:
            total = l_sum + r_sum + node.val
            best = max(best, total)
            return True, total, min(l_min, node.val), max(r_max, node.val)
        return False, 0, 0, 0

    summarize(root)
    return best

Two things change beyond the field. The biggest BST no longer wins automatically - negative keys can make a smaller BST inside it sum to more. And best can't start at 0: every single node is a BST, so with all-negative values the answer is the largest (least negative) node, and a 0 start would report a sum that no subtree has.

Where you'll see it

"Find the largest well-formed region inside a structure that may be partly malformed" - the biggest correctly ordered run in a corrupted index, the largest well-typed sub-expression in a syntax tree with errors - is this bottom-up summary shape: each piece reports validity plus just enough about itself for its parent to decide in O(1).

Which tool for which job?

You needUse
The largest BST subtreea postorder summary: (size, min, max), -1 for invalid
The highest-sum BST subtreethe same summary carrying the sum; start the best below 0
Values that can reach the int limitslong bounds (or null checks) for empty subtrees
A tree that may be a deep chainiterative postorder with a map of child summaries
Whether the whole tree is a BSTrange validation from the root

Common mistakes

MistakeWhat happensDo this instead
Comparing a node only with its direct children12 in 10's left subtree slips throughcompare with the left max and the right min
Re-validating every subtreeO(n^2) on a chainone postorder pass, O(n)
Not propagating "invalid"an ancestor of a broken subtree counts as a BSTinvalid in, invalid out
Integer.MAX_VALUE as the empty mina real MAX_VALUE leaf is judged invalidlong bounds
Starting the best sum at 0all-negative trees report 0start at minus infinity

Check yourself

Try to answer before opening each one - recalling beats re-reading.

1. In [10, 5, 15, 1, 12, null, 20], why is the whole tree not a BST, and how does the summary catch it?

Show answer

12 is in 10's left subtree but larger than 10. The left subtree (5, 1, 12) is itself a valid BST, and its summary reports max = 12. At the root, the check 10 > left.max fails, so the whole tree is invalid. A check against the direct child (5) alone would have missed it.

2. Why does the empty subtree return min = Integer.MAX_VALUE and max = Integer.MIN_VALUE, and when is that a problem?

Show answer

So that any real value satisfies node.val > left.max and node.val < right.min against an empty side, with no null checks. It breaks when a real value equals a sentinel: a node holding Integer.MAX_VALUE with no right child fails node.val < Integer.MAX_VALUE. Use long bounds (or null checks) when values can reach the int limits.

3. For the highest-sum BST subtree, why must the running best start below 0?

Show answer

Every single node is a BST, so an answer always exists - and with all-negative values it's negative (the least negative node). Starting at 0 would return 0, a sum no subtree has. (Some versions of the problem define an empty BST with sum 0 as allowed, and then 0 is correct - read which one is asked.)

Key takeaways

  • Each subtree returns a summary - valid?, size or sum, min, max - and the parent decides its own validity in O(1).
  • A node must lie strictly between its left subtree's max and its right subtree's min; comparing with direct children isn't enough.
  • "Invalid" propagates: once a child isn't a BST, no ancestor can be.
  • Empty-subtree sentinels only work while no real value can equal them; use long bounds when values span the int range.
  • Swap the ranked field (size, sum) to answer related questions - and start the running best where the answer can actually be.

Quick revise

The whole skill on one page, for the night before an interview.

This page puts the lesson and the deep dive together. Skim it before practice or an interview, and go back to the full lesson for anything you don't recognize.

Pick the tool

You needUse
Largest BST subtreepostorder (size, min, max)
Highest-sum BST subtreecarry the sum, best starts below 0
Full int rangelong bounds in a record
Deep chainsiterative postorder plus a summary map

Snippets worth knowing by heart

Java

record Summary(int size, long min, long max) {}      // size -1 = not a BST

Summary helper(TreeNode n) {
    if (n == null) return new Summary(0, Long.MAX_VALUE, Long.MIN_VALUE);
    Summary l = helper(n.left), r = helper(n.right);
    if (l.size() < 0 || r.size() < 0 || n.val <= l.max() || n.val >= r.min()) return new Summary(-1, 0, 0);
    int size = l.size() + r.size() + 1;
    best = Math.max(best, size);
    return new Summary(size, Math.min(l.min(), n.val), Math.max(r.max(), n.val));
}

Python

def helper(n):
    if n is None:
        return 0, math.inf, -math.inf
    ls, lmin, lmax = helper(n.left)
    rs, rmin, rmax = helper(n.right)
    if ls < 0 or rs < 0 or not lmax < n.val < rmin:
        return -1, 0, 0
    return ls + rs + 1, min(lmin, n.val), max(rmax, n.val)

The traps

  • Checking only the direct children: a deeper value can break an ancestor's range. Compare with the left max and right min.
  • Re-validating every subtree: a chain costs O(n^2). Return the facts from one postorder pass.
  • Not propagating "invalid": an ancestor of a broken subtree counts as a BST. Invalid in, invalid out.
  • int sentinels: a real MAX_VALUE leaf looks invalid. Use long bounds.
  • A best sum that starts at 0: an all-negative tree reports 0. Start at minus infinity.

Staff-level points, one line each

  • Re-validation: 2,001,000 visits on a 2,000-node chain and 983,041 on a 65,535-node balanced tree, against n for the summary.
  • [5, 3, 2147483647]: int sentinels reported 1, and long bounds in a record reported 3.
  • Recursion failed on a 2,000-node chain in Python and a 100,000-node chain in Java; the iterative postorder returned 2,000.
  • [0, -5, 3]: the largest BST is size 3, but the highest sum is 3 from a single node.
  • A subtree includes all of a node's descendants. Deleting nodes to make a BST is a different problem.

Say it in an interview

  • "Each subtree reports validity, size, min and max, and the parent decides in O(1)."
  • "A node must be greater than the left subtree's max and less than the right subtree's min."
  • "It's one postorder pass: O(n) time and O(h) space."
  • "For sums I'd update the best at every valid node, since the largest BST isn't always the best one."

Practice

This skill comes with 9 multiple-choice questions that tell you which misconception a wrong answer reveals and 2 graded problems. Your answers are graded and your mastery is tracked, so you'll know when the skill holds up. Running code and saving progress need a free account.

Practice Largest BST Subtree free

Size of the Largest BST Subtree

HardProCoding problemJava · Python

Given the root of a binary tree (not necessarily a BST), find the size (number of nodes) of the largest subtree that is itself a valid binary search tree.

The full statement, the examples and the hidden test suite are part of AI Proof Engineers Pro.

Maximum Sum BST Subtree

HardProCoding problemJava · Python

Given the root of a binary tree (not necessarily a BST), return the largest sum of keys over all non-empty subtrees that are valid binary search trees (left subtree values strictly smaller, right subtree values strictly larger, recursively).

The full statement, the examples and the hidden test suite are part of AI Proof Engineers Pro.

Practice Largest BST Subtree free

No card needed. Lessons, practice questions and easy problems are free forever.

Free plan, no card · 7-day Pro trial · Cancel anytime