Before attempting this problem, you should be comfortable with:
Binary Search Tree (BST) properties - Understanding that left subtree values are less than root and right subtree values are greater, which enables pruning decisions
Recursion and DFS - Traversing trees recursively and understanding how to rebuild tree structure during traversal
Tree node manipulation - Modifying parent-child relationships by reassigning left/right pointers
1. Depth First Search
Intuition
The BST property gives us a powerful pruning strategy. If the current node's value is greater than high, then the node and its entire right subtree are too large, so we can discard them and only keep the trimmed left subtree. Similarly, if the value is less than low, the node and its left subtree are too small. When the node is within range, we recursively trim both children and attach the results.
Algorithm
If the current node is null, return null (base case).
If the node's value is greater than high, return the result of trimming the left subtree (discard current node and right subtree).
If the node's value is less than low, return the result of trimming the right subtree (discard current node and left subtree).
Otherwise, recursively trim both left and right children, attach them to the current node, and return the node.
/**
* Definition for a binary tree node.
* type TreeNode struct {
* Val int
* Left *TreeNode
* Right *TreeNode
* }
*/functrimBST(root *TreeNode, low int, high int)*TreeNode {if root ==nil{returnnil}if root.Val > high {returntrimBST(root.Left, low, high)}if root.Val < low {returntrimBST(root.Right, low, high)}
root.Left =trimBST(root.Left, low, high)
root.Right =trimBST(root.Right, low, high)return root
}
/**
* Definition for a binary tree node.
* class TreeNode(var `val`: Int) {
* var left: TreeNode? = null
* var right: TreeNode? = null
* }
*/class Solution {funtrimBST(root: TreeNode?, low: Int, high: Int): TreeNode?{if(root ==null)returnnullif(root.`val` > high){returntrimBST(root.left, low, high)}if(root.`val` < low){returntrimBST(root.right, low, high)}
root.left =trimBST(root.left, low, high)
root.right =trimBST(root.right, low, high)return root
}}
/**
* Definition for a binary tree node.
* public class TreeNode {
* public var val: Int
* public var left: TreeNode?
* public var right: TreeNode?
* public init() { self.val = 0; self.left = nil; self.right = nil; }
* public init(_ val: Int) { self.val = val; self.left = nil; self.right = nil; }
* public init(_ val: Int, _ left: TreeNode?, _ right: TreeNode?) {
* self.val = val
* self.left = left
* self.right = right
* }
* }
*/classSolution{functrimBST(_ root:TreeNode?,_ low:Int,_ high:Int)->TreeNode?{guardlet root = root else{returnnil}if root.val > high {returntrimBST(root.left, low, high)}if root.val < low {returntrimBST(root.right, low, high)}
root.left=trimBST(root.left, low, high)
root.right=trimBST(root.right, low, high)return root
}}
Time & Space Complexity
Time complexity: O(n)
Space complexity: O(n) for recursion stack.
2. Iterative DFS
Intuition
We can avoid recursion by using a stack. The idea remains the same: nodes outside the range need to be replaced by their valid children. We first find a valid root, then process the tree using the stack, fixing any out-of-range children by replacing them with their appropriate grandchildren.
Algorithm
Find the new root by moving to the right child if the current root is too small, or to the left child if too large, until the root is within [low, high].
Initialize a stack with the valid root.
While the stack is not empty:
Pop a node.
If the left child exists and its value is less than low, replace it with its right child.
If the right child exists and its value is greater than high, replace it with its left child.
If any replacement was made, push the node back for reprocessing.
Otherwise, push both children (if they exist) onto the stack.
/**
* Definition for a binary tree node.
* public class TreeNode {
* public var val: Int
* public var left: TreeNode?
* public var right: TreeNode?
* public init() { self.val = 0; self.left = nil; self.right = nil; }
* public init(_ val: Int) { self.val = val; self.left = nil; self.right = nil; }
* public init(_ val: Int, _ left: TreeNode?, _ right: TreeNode?) {
* self.val = val
* self.left = left
* self.right = right
* }
* }
*/classSolution{functrimBST(_ root:TreeNode?,_ low:Int,_ high:Int)->TreeNode?{var root = root
while root !=nil&&(root!.val < low || root!.val > high){
root = root!.val < low ? root!.right: root!.left}var stack:[TreeNode?]=[root]while!stack.isEmpty {guardlet node = stack.removeLast()else{continue}let leftOut = node.left!=nil&& node.left!.val < low
let rightOut = node.right!=nil&& node.right!.val > high
if leftOut { node.left= node.left?.right}if rightOut { node.right= node.right?.left}if leftOut || rightOut {
stack.append(node)}else{if node.left!=nil{ stack.append(node.left)}if node.right!=nil{ stack.append(node.right)}}}return root
}}
Time & Space Complexity
Time complexity: O(n)
Space complexity: O(n)
3. Iterative DFS (Optimal)
Intuition
Instead of using a stack, we can trim the tree in two linear passes. After finding a valid root, we traverse down the left spine fixing any nodes that fall below low, then traverse down the right spine fixing any nodes that exceed high. This works because in a BST, once we fix a node on one side, we only need to continue checking in that direction.
Algorithm
Find a valid root by skipping nodes outside the range.
Save the valid root as tmpRoot.
Traverse the left spine: while there is a left child with value less than low, replace it with its right child. Move to the next left child.
Reset to tmpRoot and traverse the right spine: while there is a right child with value greater than high, replace it with its left child. Move to the next right child.
/**
* Definition for a binary tree node.
* public class TreeNode {
* public var val: Int
* public var left: TreeNode?
* public var right: TreeNode?
* public init() { self.val = 0; self.left = nil; self.right = nil; }
* public init(_ val: Int) { self.val = val; self.left = nil; self.right = nil; }
* public init(_ val: Int, _ left: TreeNode?, _ right: TreeNode?) {
* self.val = val
* self.left = left
* self.right = right
* }
* }
*/classSolution{functrimBST(_ root:TreeNode?,_ low:Int,_ high:Int)->TreeNode?{var root = root
while root !=nil&&(root!.val < low || root!.val > high){
root = root!.val < low ? root!.right: root!.left}let tmpRoot = root
while root !=nil{while root!.left!=nil&& root!.left!.val < low {
root!.left= root!.left?.right}
root = root!.left}
root = tmpRoot
while root !=nil{while root!.right!=nil&& root!.right!.val > high {
root!.right= root!.right?.left}
root = root!.right}return tmpRoot
}}
Time & Space Complexity
Time complexity: O(n)
Space complexity: O(1) extra space.
Common Pitfalls
Forgetting to Recursively Trim After Skipping a Node
When a node's value is outside the range and you skip to its child, you must continue trimming that child subtree. A common mistake is to simply return the child without recursively processing it. The child itself or its descendants may also be outside the valid range and need trimming.
Confusing Which Subtree to Keep When Node is Out of Range
When a node's value is greater than high, you should return the trimmed left subtree (not the right). When a node's value is less than low, you should return the trimmed right subtree (not the left). Mixing these up violates the BST property and produces incorrect results.
Not Handling the Case Where the Root Itself is Invalid
The root node may be outside the [low, high] range, meaning the entire original root gets discarded. A common oversight is assuming the root will always be valid. Your solution must handle finding a new valid root by traversing into the appropriate subtree before beginning the main trimming logic.