LeetCode 270 - Closest Binary Search Tree Value
The problem gives us the root of a Binary Search Tree, abbreviated as BST, and a floating point target value. Our task is to return the integer value stored in the BST that is numerically closest to the target.
Difficulty: 🟢 Easy
Topics: Binary Search, Tree, Depth-First Search, Binary Search Tree, Binary Tree
Solution
Problem Understanding
The problem gives us the root of a Binary Search Tree, abbreviated as BST, and a floating point target value. Our task is to return the integer value stored in the BST that is numerically closest to the target.
A Binary Search Tree has a very important property:
- Every node in the left subtree contains a smaller value than the current node.
- Every node in the right subtree contains a larger value than the current node.
This ordering property is the key observation that allows us to solve the problem efficiently.
The input consists of:
root, the root node of the BSTtarget, a floating point number that may or may not exist in the tree
The expected output is:
- The node value whose absolute difference from
targetis minimal - If two values are equally close, we must return the smaller one
For example, if the target is 3.7 and the tree contains values 3 and 4, then 4 is closer because:
|3.7 - 3| = 0.7|3.7 - 4| = 0.3
Therefore the answer is 4.
The constraints tell us several important things:
- The tree contains between
1and10^4nodes - Node values can be very large, up to
10^9 - The target may even be outside the range of all node values
Since the tree can contain up to ten thousand nodes, a linear traversal is acceptable, but the BST structure suggests we can do better than scanning every node.
Several edge cases are important:
- The tree may contain only one node
- The target may be smaller than every value in the BST
- The target may be larger than every value in the BST
- Two values may be equally close to the target, in which case we must return the smaller one
- The target may exactly match a node value
The problem guarantees that the tree is non-empty, so we never need to handle a null root as the entire input.
Approaches
Brute Force Approach
The simplest approach is to traverse the entire tree and compare every node value against the target.
During traversal, we keep track of:
- The currently closest value
- The smallest absolute difference seen so far
For every node:
- Compute
abs(node.val - target) - Compare it against the current best difference
- Update the answer if this node is closer
- If the difference is tied, choose the smaller value
This approach is correct because it examines every node in the tree, guaranteeing that the closest value will eventually be found.
However, this ignores the BST property entirely. Even though the tree is ordered, the brute-force solution still visits all nodes.
Optimal BST Approach
The BST property allows us to make smarter decisions.
At each node:
- If
target < node.val, then any potentially closer values must be in the left subtree - If
target > node.val, then any potentially closer values must be in the right subtree
This means we can follow a single path down the tree instead of exploring every node.
While traversing, we continuously maintain the closest value encountered so far.
The key insight is that the BST ordering lets us discard half of the remaining search space at each step, similar to binary search on a sorted array.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n) | O(h) recursive or O(n) worst-case | Traverses every node |
| Optimal | O(h) average, O(n) worst-case | O(1) iterative | Uses BST ordering to skip subtrees |
Here:
nis the number of nodeshis the height of the tree
In a balanced BST, h = log n, making the optimal solution very efficient.
Algorithm Walkthrough
- Initialize a variable called
closestwith the root value.
We need some initial candidate answer. Since the tree is guaranteed to be non-empty, the root value is a safe starting point. 2. Start traversing from the root node.
We use a pointer called current to walk through the BST iteratively.
3. At each node, compare its value with the current best answer.
We compute:
abs(current.val - target)
and compare it against:
abs(closest - target)
If the current node is closer, we update closest.
If both distances are equal, we choose the smaller value because the problem explicitly requires that tie-breaking rule. 4. Decide which subtree to explore next.
The BST property tells us where better candidates could exist.
- If
target < current.val, move left - If
target > current.val, move right - If they are equal, we can immediately return because no value can be closer than an exact match
- Continue until the traversal reaches a null node.
Once current becomes None or nil, we have exhausted the only relevant search path.
6. Return the stored closest value.
Why it works
The algorithm works because a BST is ordered. At every node, we know that one entire subtree cannot contain values that move closer toward the target.
If the target is smaller than the current value, then every value in the right subtree is even larger, making them less promising. Similarly, if the target is larger, the left subtree becomes less useful.
By always moving toward the side where closer values could exist, we efficiently search only the relevant path while continuously maintaining the best candidate encountered so far.
Python Solution
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
from typing import Optional
class Solution:
def closestValue(self, root: Optional[TreeNode], target: float) -> int:
closest = root.val
current = root
while current:
current_diff = abs(current.val - target)
closest_diff = abs(closest - target)
if (
current_diff < closest_diff
or (
current_diff == closest_diff
and current.val < closest
)
):
closest = current.val
if target < current.val:
current = current.left
elif target > current.val:
current = current.right
else:
return current.val
return closest
The implementation starts by storing the root value as the current best candidate. Since the tree is guaranteed to contain at least one node, this initialization is always valid.
The while current: loop walks through the BST iteratively. This avoids recursion and keeps the space complexity constant.
Inside the loop, the code computes the distance between the current node and the target, then compares it with the best distance seen so far.
The update condition handles two cases:
- The current node is strictly closer
- The current node is equally close but smaller, satisfying the tie-breaking requirement
After updating the candidate answer if necessary, the traversal direction is determined using BST ordering:
- Move left when the target is smaller
- Move right when the target is larger
- Return immediately on an exact match
The loop ends once the traversal falls off the tree, and the best stored value is returned.
Go Solution
import "math"
/**
* Definition for a binary tree node.
* type TreeNode struct {
* Val int
* Left *TreeNode
* Right *TreeNode
* }
*/
func closestValue(root *TreeNode, target float64) int {
closest := root.Val
current := root
for current != nil {
currentDiff := math.Abs(float64(current.Val) - target)
closestDiff := math.Abs(float64(closest) - target)
if currentDiff < closestDiff ||
(currentDiff == closestDiff && current.Val < closest) {
closest = current.Val
}
if target < float64(current.Val) {
current = current.Left
} else if target > float64(current.Val) {
current = current.Right
} else {
return current.Val
}
}
return closest
}
The Go implementation follows the same logic as the Python solution.
One important difference is that Go requires explicit type conversion between integers and floating point values. Since target is a float64, node values must be converted before subtraction and comparison.
Go also uses nil instead of None for null pointers.
The iterative traversal avoids recursion, so no additional stack space is required beyond a few local variables.
Worked Examples
Example 1
Input:
root = [4,2,5,1,3]
target = 3.714286
BST structure:
4
/ \
2 5
/ \
1 3
Traversal process:
| Step | Current Node | Closest Before | Current Difference | Closest Difference | Updated Closest | Next Move |
|---|---|---|---|---|---|---|
| 1 | 4 | 4 | 0.285714 | 0.285714 | 4 | Move left |
| 2 | 2 | 4 | 1.714286 | 0.285714 | 4 | Move right |
| 3 | 3 | 4 | 0.714286 | 0.285714 | 4 | Move right |
| 4 | nil | 4 | - | - | 4 | Stop |
Final answer:
4
Example 2
Input:
root = [1]
target = 4.428571
Traversal process:
| Step | Current Node | Closest Before | Current Difference | Closest Difference | Updated Closest | Next Move |
|---|---|---|---|---|---|---|
| 1 | 1 | 1 | 3.428571 | 3.428571 | 1 | Move right |
| 2 | nil | 1 | - | - | 1 | Stop |
Final answer:
1
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(h) average, O(n) worst-case | Traverses one BST path |
| Space | O(1) | Uses iterative traversal |
The algorithm only follows a single path from the root toward a leaf. In a balanced BST, the height is logarithmic, so the runtime becomes O(log n).
In the worst case, the BST may be completely skewed like a linked list, making the height equal to n. In that case, the runtime degrades to O(n).
The implementation is iterative and stores only a few variables, so the auxiliary space usage remains constant.
Test Cases
# Helper TreeNode class for testing
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
class Solution:
def closestValue(self, root, target):
closest = root.val
current = root
while current:
current_diff = abs(current.val - target)
closest_diff = abs(closest - target)
if (
current_diff < closest_diff
or (
current_diff == closest_diff
and current.val < closest
)
):
closest = current.val
if target < current.val:
current = current.left
elif target > current.val:
current = current.right
else:
return current.val
return closest
solver = Solution()
# Example 1
root1 = TreeNode(4,
TreeNode(2,
TreeNode(1),
TreeNode(3)),
TreeNode(5))
assert solver.closestValue(root1, 3.714286) == 4 # standard example
# Example 2
root2 = TreeNode(1)
assert solver.closestValue(root2, 4.428571) == 1 # single node tree
# Exact match
root3 = TreeNode(4,
TreeNode(2),
TreeNode(6))
assert solver.closestValue(root3, 6.0) == 6 # exact target exists
# Target smaller than all nodes
root4 = TreeNode(10,
TreeNode(5),
TreeNode(15))
assert solver.closestValue(root4, -100.0) == 5 # minimum node closest
# Target larger than all nodes
root5 = TreeNode(10,
TreeNode(5),
TreeNode(15))
assert solver.closestValue(root5, 100.0) == 15 # maximum node closest
# Tie case, smaller value should win
root6 = TreeNode(4,
TreeNode(2),
TreeNode(6))
assert solver.closestValue(root6, 5.0) == 4 # equal distance between 4 and 6
# Left-skewed tree
root7 = TreeNode(5,
TreeNode(4,
TreeNode(3,
TreeNode(2,
TreeNode(1)))))
assert solver.closestValue(root7, 2.8) == 3 # skewed structure
# Right-skewed tree
root8 = TreeNode(1,
None,
TreeNode(2,
None,
TreeNode(3,
None,
TreeNode(4))))
assert solver.closestValue(root8, 3.6) == 4 # skewed structure
# Large values
root9 = TreeNode(1000000000,
TreeNode(500000000),
TreeNode(1500000000))
assert solver.closestValue(root9, 999999999.2) == 1000000000 # large integers
| Test | Why |
|---|---|
| Standard balanced BST example | Verifies normal traversal behavior |
| Single node tree | Confirms minimal input handling |
| Exact target match | Ensures immediate return correctness |
| Target smaller than all nodes | Tests left boundary behavior |
| Target larger than all nodes | Tests right boundary behavior |
| Equal distance tie | Validates smaller-value tie-breaking rule |
| Left-skewed tree | Tests worst-case traversal shape |
| Right-skewed tree | Tests opposite skew direction |
| Large integer values | Ensures numeric comparisons remain correct |
Edge Cases
One important edge case occurs when the tree contains only a single node. A careless implementation might assume the existence of children and attempt invalid traversal operations. In this implementation, the algorithm safely initializes closest with the root value and simply exits once traversal reaches None or nil.
Another critical edge case is when the target lies outside the range of all values in the BST. For example, the target may be much smaller than the minimum node value or much larger than the maximum node value. In these situations, the traversal naturally continues toward the extreme edge of the tree, and the closest boundary value is retained as the answer.
The tie-breaking rule is also easy to mishandle. If two values are equally close to the target, the problem requires returning the smaller value. A naive implementation that updates only on strictly smaller distance may incorrectly return the larger value depending on traversal order. This solution explicitly checks for equal differences and prefers the smaller integer.
A final edge case involves highly unbalanced trees. A BST can degenerate into a linked list, producing height n instead of log n. Recursive implementations may risk stack overflow in some environments. The iterative approach used here avoids recursion entirely and handles skewed trees safely.