Browse curriculum

Largest BST Subtree

Find the size of the largest subtree of a binary tree that is a valid binary search tree, by passing each subtree's BST status, size, minimum and maximum up to its parent.

Problem Understanding

Largest BST Subtree (LeetCode 333): given the root of a binary tree, return the number of nodes in the largest subtree that is also a binary search tree. A subtree includes all of a node's descendants.

Examples: root = [10,5,15,1,8,null,7] → 3; root = [4,2,7,2,3,5,null,2,null,null,null,null,null,1] → 2.

The Intuition: Summaries Flow Up

Whether a subtree is a BST depends only on four facts about each child subtree:

  1. Is it a BST? If either side is not, neither is this one, or any ancestor.
  2. Its min and max. The left max must be below the node and the right min above it.
  3. Its size. A valid subtree's size is left + right + 1.

Computing these in post-order makes each node O(1). The min/max bounds are the same idea as Validate Binary Search Tree, and returning several values from each call is the pattern of Return Values in DFS.

Interactive Walkthrough

ABOVE A NODE: SIZE OF THE BST ROOTED THERE, ✗ IF NONE

POST-ORDER

node

:

largest

0

 

Post-order: each node waits for both children, then decides if its subtree is a BST

Next

1: left max −∞ < 1 < right min +∞, both sides BSTs: a BST of size 1, the largest so far

Free preview: the first 3 of 8 steps

tree

Each finished node shows the size of the BST rooted at it, or ✗. Try Hidden break: every parent and child look in order, but a value deep in the right subtree breaks the whole tree.

Dry Run Table

Input: root = [10,5,15,1,8,null,7].

Node (post-order) Check Result
1 leaf BST, size 1
8 leaf BST, size 1
5 1 < 5 < 8 BST, size 3 (largest so far)
7 leaf BST, size 1
15 right min 7 ≤ 15 not a BST
10 right subtree not a BST not a BST

Answer: 3.

The Solution Template

Post-order DFS returning (isBST, size, min, max).

Largest BST Subtree Code
1var largestBSTSubtree = function(root) {
2 let best = 0;
3 const walk = (node) => {
4 if (!node) return [true, 0, Infinity, -Infinity];
5 const [lBst, lSize, lLo, lHi] = walk(node.left);
6 const [rBst, rSize, rLo, rHi] = walk(node.right);
7 if (lBst && rBst && lHi < node.val && node.val < rLo) {
8 const size = lSize + rSize + 1;
9 best = Math.max(best, size);
10 return [true, size, Math.min(lLo, node.val), Math.max(rHi, node.val)];
11 }
12 return [false, 0, 0, 0];
13 };
14 walk(root);
15 return best;
16};

Edge Cases & Common Mistakes

  • Empty tree: the answer is 0.
  • Duplicates: LeetCode's BST has strictly smaller left and larger right values, so an equal value breaks it.
  • Comparing only with direct children: misses violations deeper down; compare with the subtree min and max.
  • Returning a subtree's size when it is not a BST: an ancestor could then count nodes from an invalid subtree.
  • Extreme values: with INT_MIN/INT_MAX sentinels, a node holding exactly those values needs long or nullable bounds.

Summary

In post-order, each node combines its children's (isBST, size, min, max): it is a BST when both children are and left max < val < right min. Track the largest size. O(n) time, O(h) space.

More practice: DSA interview questions with answers · patterns cheat sheet

The approach, step by step

  1. Recurse

    Get (isBST, size, min, max) for the left and right subtrees; an empty subtree is (true, 0, +∞, −∞).

  2. Combine

    If both are BSTs and left max < node.val < right min, this subtree is a BST of size left + right + 1; update the best.

  3. Otherwise

    Report this subtree as not a BST, so no ancestor can be one either.

Frequently asked questions

Why is checking each node against its two children not enough?

The BST rule covers whole subtrees: every value on the left must be smaller than the node, not just the left child. In [3,2,4,null,null,1], 2 < 3 < 4 looks fine, but 1 is in 3's right subtree. Passing up each subtree's min and max catches that: the right subtree's minimum is 1, which is not greater than 3.


Why must it be post-order?

A node can only be judged once both of its subtrees have been judged and summarised. Post-order visits the children first, so every node decides in O(1) from two ready-made summaries, and the whole tree takes O(n).


What does an empty child return?

A BST of size 0 with min = +∞ and max = −∞. Then the checks left max < node.val and node.val < right min always pass on a missing side, and the min and max of the parent come out right without special cases.


What is the brute-force approach?

For every node, validate its subtree as a BST (Validate Binary Search Tree) and count its nodes: O(n) per node, O(n²) in total. The post-order summary removes the repeated work.

New to the technique behind this problem? Watch it run step by step:

Depth First Search Visualization

Applying for software engineering roles? Free, no sign-up:

AI resume checker: match your resume to the job description