AlgoMaster Logo

Trim a Binary Search Tree

Last Updated: March 29, 2026

medium
5 min read

Understanding the Problem

We have a BST and a range [low, high]. We need to remove every node whose value falls outside this range, and return the modified tree. The critical requirement is that the relative structure of the remaining nodes stays the same. If node A was an ancestor of node B before trimming, and both survive, then A must still be an ancestor of B after trimming.

What makes this problem interesting is that removing a node doesn't necessarily mean discarding its entire subtree. Consider a node with value 0 when low = 1. The node itself is too small, but its right child (say, value 2) might be perfectly valid. So we can't just chop off a subtree and call it done. We need to selectively keep parts of the tree.

The key observation comes from the BST property. If a node's value is less than low, then its entire left subtree is also less than low (because everything in the left subtree is smaller than the node). So we can safely discard the node and its left subtree, but we still need to check the right subtree. The mirror logic applies when a node's value is greater than high.

Key Constraints:

  • 1 <= number of nodes <= 10^4 → The tree is non-empty and has at most 10,000 nodes. O(n) approaches are perfectly fine, and even O(n log n) would work.
  • 0 <= Node.val <= 10^4 → Non-negative integer values. No special handling for negatives needed.
  • Each node has a unique value → No duplicates, so the BST property is strict.
  • root is a valid BST → We can rely on BST ordering for pruning decisions.
  • 0 <= low <= high <= 10^4 → The range is always valid (low never exceeds high). We don't need to handle an empty range.

Approach 1: Recursive DFS

Intuition

The most natural way to approach this is with recursion. At each node, we ask: "Is this node's value within the range [low, high]?" Based on the answer, three things can happen.

If the node's value is less than low, the node itself is too small. But here's the thing: because of the BST property, the node's entire left subtree is also too small (every value in the left subtree is less than the node, which is already less than low). So we can throw away the node and its left subtree completely. But the right subtree might contain valid nodes. A node with value 0 could have a right child with value 5, and if low = 1, that 5 is perfectly fine. So we recursively trim the right subtree and return whatever survives.

The mirror case applies when the node's value exceeds high. The node and its entire right subtree are too large, but the left subtree might have valid nodes.

If the node's value is within [low, high], we keep it. But its children might still have out-of-range nodes deeper down. So we recursively trim both the left and right subtrees and attach the results.

This recursive approach is clean because each call handles exactly one node and delegates the rest. The tree naturally rebuilds itself from the bottom up as the recursion unwinds.

Algorithm

  1. If the root is null, return null (base case).
  2. If root.val < low, the entire left subtree is also out of range. Recursively trim the right subtree and return the result.
  3. If root.val > high, the entire right subtree is also out of range. Recursively trim the left subtree and return the result.
  4. If root.val is within [low, high], keep the node. Recursively trim the left subtree (assign to root.left) and the right subtree (assign to root.right).
  5. Return the root.

Example Walkthrough

1Visit node 3: value in [1,3], keep it. Trim both subtrees.
3keep0214
1/6

Code

The recursive approach is already O(n) time, which is optimal. But the recursion stack can grow to O(n) for a skewed tree. What if we could achieve the same result iteratively with O(1) extra space?

Approach 2: Iterative

Intuition

The iterative approach applies the exact same logic as the recursive one, just without the call stack. The trick is to break the problem into three phases.

First, we need to find a valid root. The current root might be outside the range. If it's too small, we move to its right child. If it's too large, we move to its left child. We keep doing this until we find a node within [low, high] or run out of nodes.

Once we have a valid root, we need to trim nodes that are too small from the left subtree and nodes that are too large from the right subtree. Here's the nice part: we can handle these separately.

For the left subtree, we walk down the left chain. At each node, if its left child has a value less than low, that child and its entire left subtree are too small. So we replace the child with its right subtree (which might contain valid nodes). We keep doing this until all left-side nodes are within range.

The mirror logic applies to the right subtree: walk down the right chain, and whenever a right child exceeds high, replace it with its left subtree.

Algorithm

  1. Find a valid root: while root is not null and root's value is out of range, move to the appropriate child.
  2. Trim the left subtree: starting from root, walk down the left children. If any left child's value is less than low, replace it with its right child.
  3. Trim the right subtree: starting from root, walk down the right children. If any right child's value is greater than high, replace it with its left child.
  4. Return the root.

Example Walkthrough

1Phase 1: Check root (3). Value in [1,3], root is valid.
3valid root0214
1/5

Code