Trim a Binary Search Tree

MediumBinary Search Tree · Recursion🧩 Ordered Set (TreeMap)LeetCode #669 ↗Updated

Problem

Given the root of a binary search tree and a range [low, high], delete every node whose value falls outside the range and return the new root. Surviving nodes must keep their relative structure: if one survivor was a descendant of another, it still is. The trimmed tree is unique.
Inputroot = [1,0,2], low = 1, high = 2Output[1,null,2]
Inputroot = [3,0,4,null,2,null,null,1], low = 1, high = 3Output[3,2,null,1]
Inputroot = [5,3,8], low = 9, high = 12Output[]

1 ≤ nodes ≤ 104  ·  0 ≤ Node.val ≤ 104, all distinct  ·  0 ≤ low ≤ high ≤ 104

Try it yourself

Java
class Solution {
    public TreeNode trimBST(TreeNode root, int low, int high) {
        
    }
}
Your code is saved in this browser.Submit on LeetCode ↗
Key insight
If a node is below low, it and its whole left subtree are gone — the answer for that spot is the trimmed right subtree (and vice versa above high). Only the two boundary paths of the tree ever need rewiring.
PatternOrdered Set (TreeMap)

Instinct Keys keep arriving or leaving and you must ask "what is the nearest key below/above x?" or "current min/max" each time - reach for TreeMap/TreeSet.

Memory hack A HashMap with a sense of direction - floor looks down, ceiling looks up, and both answer in log time while the data keeps changing.

Open the Ordered Set (TreeMap) guide →

Approaches

Brute ForceFilter Pre-order, Rebuild by Insertion

Time O(n · h)Space O(n)

Record the in-range values in pre-order, then insert them one by one into an empty BST. A BST is fully determined by the pre-order sequence of its values, and removing nodes keeps the survivors' pre-order order intact, so the rebuild yields exactly the trimmed shape.

Java
public TreeNode trimBST(TreeNode root, int low, int high) {
    List<Integer> kept = new ArrayList<>();
    preorder(root, low, high, kept);
    TreeNode res = null;
    for (int v : kept) res = insert(res, v);
    return res;
}
private void preorder(TreeNode node, int low, int high, List<Integer> kept) {
    if (node == null) return;
    if (node.val >= low && node.val <= high) kept.add(node.val);
    preorder(node.left, low, high, kept);
    preorder(node.right, low, high, kept);
}
private TreeNode insert(TreeNode root, int v) {
    TreeNode node = new TreeNode(v);
    if (root == null) return node;
    TreeNode cur = root;
    while (true) {
        if (v < cur.val) {
            if (cur.left == null) { cur.left = node; break; }
            cur = cur.left;
        } else {
            if (cur.right == null) { cur.right = node; break; }
            cur = cur.right;
        }
    }
    return root;
}

Correct but allocates a whole new tree; insertion is O(n²) on a skewed tree.

At a glance

LevelApproachTimeSpace
Brute ForceFilter Pre-order, Rebuild by InsertionO(n · h)O(n)
BetterRecursive TrimO(n)O(h)
OptimalIterative Boundary TrimmingO(h)O(1)
Common pitfalls
  • Returning null for an out-of-range node — its other subtree may contain valid nodes.
  • Trimming only the child and forgetting the replacement may itself be out of range (needs a loop or recursion).
  • Rebuilding a balanced tree from sorted survivors — correct values, wrong structure.
Interview tips
  • Start from the recursive rule: below range → go right, above range → go left.
  • Point out the BST property makes whole subtrees disposable without visiting them.
  • Offer the iterative O(h)-time version as the follow-up for deep or skewed trees.
Pattern
BST range pruning → when a value range lets you discard or keep whole subtrees at once.
My notes saved in this browser