270. Closest Binary Search Tree Value 🔒
Problem Description
You are given the root of a binary search tree (BST) and a target value (which can be a floating-point number). Your task is to find the value in the BST that is closest to the target.
The "closest" value means the node value that has the minimum absolute difference with the target. In other words, you need to find a node value v
such that |v - target|
is minimized.
If there are multiple node values that have the same minimum distance to the target (a tie situation), you should return the smallest value among them.
For example:
- If the BST contains values
[4, 2, 5, 1, 3]
and the target is3.714286
, the closest value would be4
since|4 - 3.714286| = 0.285714
is the smallest difference. - If the target is exactly between two values, like
3.5
with nodes3
and4
both having a distance of0.5
, you would return3
(the smaller value).
The solution leverages the BST property where left subtree values are smaller than the root, and right subtree values are larger. This allows for efficient traversal by comparing the target with the current node value to decide whether to go left or right, while keeping track of the closest value found so far.
Flowchart Walkthrough
First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:
Is it a graph?
- Yes: While this is a binary search tree problem, a tree is a special type of graph (specifically, a connected acyclic graph). The BST has nodes connected by edges (parent-child relationships).
Is it a tree?
- Yes: The problem explicitly states we're working with a binary search tree, which is a tree data structure where each node has at most two children.
DFS
- Since we answered "yes" to the tree question, the flowchart leads us directly to DFS (Depth-First Search).
Conclusion: The flowchart suggests using DFS for this problem.
This makes sense because:
- We need to traverse the tree to examine node values and compare them with the target
- DFS allows us to efficiently navigate the BST by leveraging its ordering property (left subtree has smaller values, right subtree has larger values)
- We can recursively explore either the left or right subtree based on how the current node's value compares to the target, effectively pruning unnecessary branches
- During the traversal, we maintain and update the closest value found so far
The DFS approach in the solution recursively visits nodes, updates the answer when a closer value is found, and intelligently chooses whether to explore the left or right subtree based on the BST property.
Intuition
The key insight comes from understanding the binary search tree property: for any node, all values in its left subtree are smaller, and all values in its right subtree are larger. This ordered structure allows us to make intelligent decisions about which direction to search.
Think of it like searching for a house number on a street where houses are numbered in order. If you're looking for house #50 and you're currently at house #30, you know you need to go forward (right). If you're at house #70, you need to go backward (left).
When we're at any node in the BST, we can ask: "Is the target value smaller or larger than the current node's value?" This comparison tells us which subtree potentially contains values closer to our target:
- If
target < node.val
, closer values might exist in the left subtree (smaller values) - If
target > node.val
, closer values might exist in the right subtree (larger values)
However, we can't immediately discard the current node! Even if the target is smaller than the current value, the current node might still be the closest. For example, if we have a node with value 10
and the target is 9.9
, we should explore the left subtree, but 10
might end up being the closest value if no nodes exist between 9.9
and 10
.
This leads to our strategy:
- At each node, calculate the absolute difference
|node.val - target|
- Keep track of the node with the minimum difference seen so far
- If there's a tie (same difference), choose the smaller value
- Continue searching in the direction where closer values might exist
- The recursion naturally handles visiting all potentially relevant nodes
The beauty of this approach is that we don't need to visit every node in the tree. The BST property allows us to prune entire subtrees that can't possibly contain the answer, making the algorithm efficient with O(h)
time complexity where h
is the height of the tree.
Solution Approach
The solution implements a recursive DFS approach to traverse the binary search tree and find the closest value to the target.
Implementation Details:
We define a recursive helper function dfs(node)
that performs the traversal. The function maintains two variables in the outer scope:
ans
: stores the node value that is currently closest to the targetdiff
: stores the minimum absolute difference found so far (initialized to infinity)
Step-by-step Algorithm:
-
Base Case: If the current node is
None
, we return immediately (end of path reached). -
Calculate Distance: For the current node, we calculate the absolute difference between its value and the target:
nxt = abs(target - node.val)
-
Update Answer: We update our answer in two cases:
- If the current difference
nxt
is smaller than the best differencediff
we've seen - If the difference is the same but the current node's value is smaller (handling ties)
if nxt < diff or (nxt == diff and node.val < ans): diff = nxt ans = node.val
- If the current difference
-
Choose Direction: Based on the BST property, we decide which subtree to explore:
node = node.left if target < node.val else node.right
If the target is less than the current node's value, we go left (to find smaller values). Otherwise, we go right (to find larger values).
-
Recursive Call: We continue the search in the chosen direction:
dfs(node)
Why This Works:
The algorithm leverages the BST property to avoid unnecessary traversals. While we might miss the closest value if we only went in one direction, by checking each node along our path and updating the answer before choosing a direction, we ensure we don't miss any potential candidates.
For example, if we're at node with value 5
and the target is 4.6
, we'll:
- Record
5
as a candidate (difference = 0.4) - Move left to find values smaller than
5
- If we find
4
(difference = 0.6), we keep5
as the answer - If we find
4.5
(difference = 0.1), we update to4.5
The time complexity is O(h)
where h
is the height of the tree, as we follow a single path from root to leaf. The space complexity is also O(h)
due to the recursion stack.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through finding the closest value to target 4.2
in this BST:
5 / \ 3 7 / \ \ 1 4 9
Step 1: Start at root (value = 5)
- Calculate difference:
|5 - 4.2| = 0.8
- Update:
ans = 5
,diff = 0.8
- Since
4.2 < 5
, go left to node 3
Step 2: Visit node 3
- Calculate difference:
|3 - 4.2| = 1.2
- Compare:
1.2 > 0.8
, so don't update answer - Since
4.2 > 3
, go right to node 4
Step 3: Visit node 4
- Calculate difference:
|4 - 4.2| = 0.2
- Compare:
0.2 < 0.8
, so update answer - Update:
ans = 4
,diff = 0.2
- Since
4.2 > 4
, we would go right, but right child isNone
Step 4: End of path
- Return with final answer:
4
The algorithm found the closest value 4
with a difference of 0.2
from the target 4.2
.
Example with a tie:
If the target was 4.5
instead:
- Node 5: difference =
0.5
- Node 4: difference =
0.5
(same difference) - Since both have the same difference but
4 < 5
, we return4
Notice how we only visited 3 nodes instead of all 6 nodes in the tree, demonstrating the efficiency of using the BST property to guide our search.
Solution Implementation
1# Definition for a binary tree node.
2# class TreeNode:
3# def __init__(self, val=0, left=None, right=None):
4# self.val = val
5# self.left = left
6# self.right = right
7
8from typing import Optional
9from math import inf
10
11class Solution:
12 def closestValue(self, root: Optional[TreeNode], target: float) -> int:
13 """
14 Find the value in a BST that is closest to the target.
15 If there are multiple values with the same difference, return the smaller value.
16
17 Args:
18 root: Root node of the binary search tree
19 target: Target value to find the closest node value to
20
21 Returns:
22 The closest integer value in the BST to the target
23 """
24
25 def dfs(node: Optional[TreeNode]) -> None:
26 """
27 Depth-first search to find the closest value to target.
28 Utilizes BST property to prune unnecessary branches.
29 """
30 # Base case: reached a null node
31 if node is None:
32 return
33
34 # Calculate the absolute difference between current node value and target
35 current_difference = abs(target - node.val)
36
37 # Access the outer scope variables
38 nonlocal closest_value, min_difference
39
40 # Update the closest value if:
41 # 1. Current difference is smaller than the minimum found so far, OR
42 # 2. Difference is the same but current value is smaller (tiebreaker)
43 if current_difference < min_difference or \
44 (current_difference == min_difference and node.val < closest_value):
45 min_difference = current_difference
46 closest_value = node.val
47
48 # Leverage BST property: go left if target is smaller, right otherwise
49 # This ensures we explore the most promising path
50 if target < node.val:
51 next_node = node.left
52 else:
53 next_node = node.right
54
55 # Continue searching in the chosen direction
56 dfs(next_node)
57
58 # Initialize variables to track the closest value and its difference
59 closest_value = 0 # Will be updated during traversal
60 min_difference = inf # Start with infinity to ensure first node updates it
61
62 # Start the depth-first search from root
63 dfs(root)
64
65 return closest_value
66
1/**
2 * Definition for a binary tree node.
3 * public class TreeNode {
4 * int val;
5 * TreeNode left;
6 * TreeNode right;
7 * TreeNode() {}
8 * TreeNode(int val) { this.val = val; }
9 * TreeNode(int val, TreeNode left, TreeNode right) {
10 * this.val = val;
11 * this.left = left;
12 * this.right = right;
13 * }
14 * }
15 */
16class Solution {
17 // Variable to store the closest value to target
18 private int closestValue;
19
20 // Target value to find the closest node value to
21 private double target;
22
23 // Minimum difference found so far between a node value and target
24 private double minDifference = Double.MAX_VALUE;
25
26 /**
27 * Finds the value in a BST that is closest to the target.
28 *
29 * @param root The root node of the binary search tree
30 * @param target The target value to find the closest value to
31 * @return The node value in the BST closest to the target
32 */
33 public int closestValue(TreeNode root, double target) {
34 this.target = target;
35 findClosestValue(root);
36 return closestValue;
37 }
38
39 /**
40 * Performs a depth-first search to find the node with value closest to target.
41 * Uses BST property to optimize search by only traversing relevant subtree.
42 *
43 * @param node The current node being processed
44 */
45 private void findClosestValue(TreeNode node) {
46 // Base case: if node is null, return
47 if (node == null) {
48 return;
49 }
50
51 // Calculate the absolute difference between current node value and target
52 double currentDifference = Math.abs(node.val - target);
53
54 // Update closest value if current node is closer to target
55 // In case of tie, choose the smaller value
56 if (currentDifference < minDifference ||
57 (currentDifference == minDifference && node.val < closestValue)) {
58 minDifference = currentDifference;
59 closestValue = node.val;
60 }
61
62 // Use BST property to determine which subtree to explore
63 // If target is less than current node value, go left; otherwise, go right
64 if (target < node.val) {
65 findClosestValue(node.left);
66 } else {
67 findClosestValue(node.right);
68 }
69 }
70}
71
1/**
2 * Definition for a binary tree node.
3 * struct TreeNode {
4 * int val;
5 * TreeNode *left;
6 * TreeNode *right;
7 * TreeNode() : val(0), left(nullptr), right(nullptr) {}
8 * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
9 * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
10 * };
11 */
12class Solution {
13public:
14 /**
15 * Finds the value in the BST that is closest to the target.
16 * @param root - The root of the binary search tree
17 * @param target - The target value to find the closest value to
18 * @return The node value closest to the target
19 */
20 int closestValue(TreeNode* root, double target) {
21 int closestVal = root->val;
22 double minDifference = INT_MAX;
23
24 // Lambda function to traverse the BST and find the closest value
25 function<void(TreeNode*)> traverse = [&](TreeNode* currentNode) {
26 // Base case: if node is null, return
27 if (!currentNode) {
28 return;
29 }
30
31 // Calculate the absolute difference between current node value and target
32 double currentDifference = abs(currentNode->val - target);
33
34 // Update the closest value if:
35 // 1. Current difference is smaller than minimum difference, OR
36 // 2. Differences are equal but current value is smaller (tie-breaker)
37 if (currentDifference < minDifference ||
38 (currentDifference == minDifference && currentNode->val < closestVal)) {
39 minDifference = currentDifference;
40 closestVal = currentNode->val;
41 }
42
43 // Utilize BST property to navigate:
44 // - Go left if target is smaller than current node value
45 // - Go right if target is greater than or equal to current node value
46 TreeNode* nextNode = (target < currentNode->val) ? currentNode->left : currentNode->right;
47 traverse(nextNode);
48 };
49
50 // Start the traversal from the root
51 traverse(root);
52
53 return closestVal;
54 }
55};
56
1/**
2 * Definition for a binary tree node.
3 * class TreeNode {
4 * val: number
5 * left: TreeNode | null
6 * right: TreeNode | null
7 * constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
8 * this.val = (val===undefined ? 0 : val)
9 * this.left = (left===undefined ? null : left)
10 * this.right = (right===undefined ? null : right)
11 * }
12 * }
13 */
14
15/**
16 * Finds the value in a Binary Search Tree (BST) that is closest to the target value.
17 * If multiple values have the same distance to target, returns the smallest value.
18 *
19 * @param root - The root node of the BST
20 * @param target - The target value to find the closest match for
21 * @returns The node value in the BST that is closest to the target
22 */
23function closestValue(root: TreeNode | null, target: number): number {
24 // Store the closest value found so far
25 let closestVal: number = 0;
26 // Store the minimum difference between target and any node value
27 let minDifference: number = Number.POSITIVE_INFINITY;
28
29 /**
30 * Performs depth-first search traversal on the BST to find the closest value.
31 * Uses BST property to optimize search by choosing left or right subtree based on target comparison.
32 *
33 * @param currentNode - The current node being processed
34 */
35 const traverseTree = (currentNode: TreeNode | null): void => {
36 // Base case: if node is null, stop recursion
37 if (!currentNode) {
38 return;
39 }
40
41 // Calculate absolute difference between target and current node value
42 const currentDifference: number = Math.abs(target - currentNode.val);
43
44 // Update closest value if current node is closer to target
45 // or if difference is same but current value is smaller
46 if (currentDifference < minDifference ||
47 (currentDifference === minDifference && currentNode.val < closestVal)) {
48 minDifference = currentDifference;
49 closestVal = currentNode.val;
50 }
51
52 // Navigate to appropriate subtree based on BST property
53 // If target is less than current value, go left; otherwise, go right
54 const nextNode: TreeNode | null = target < currentNode.val ?
55 currentNode.left : currentNode.right;
56
57 // Continue traversal with the selected subtree
58 traverseTree(nextNode);
59 };
60
61 // Start the traversal from root
62 traverseTree(root);
63
64 return closestVal;
65}
66
Time and Space Complexity
The time complexity is O(h)
where h
is the height of the binary search tree. In the worst case when the tree is completely unbalanced (essentially a linked list), h = n
, making the time complexity O(n)
. In the best case when the tree is perfectly balanced, h = log(n)
, making the time complexity O(log n)
. The algorithm leverages the BST property by choosing to traverse either left or right based on the comparison between target and current node value, visiting at most one node per level.
The space complexity is O(h)
due to the recursive call stack, where h
is the height of the tree. In the worst case of an unbalanced tree, this becomes O(n)
. In the best case of a balanced tree, this is O(log n)
. The recursive DFS function creates a new stack frame for each level of recursion, and the maximum depth of recursion equals the height of the tree.
Note: The reference answer states both complexities as O(n)
, which represents the worst-case scenario when the BST is completely unbalanced.
Common Pitfalls
1. Not Handling Tie-Breaking Correctly
The Pitfall: When two node values have the same distance from the target, forgetting to return the smaller value can lead to incorrect results. For instance, if target is 2.5
and the BST contains both 2
and 3
, both have a distance of 0.5
. The problem requires returning 2
(the smaller value), but a naive implementation might return whichever was encountered last.
Incorrect Implementation:
# Wrong: Only checks if difference is smaller, ignores ties if current_difference < min_difference: min_difference = current_difference closest_value = node.val
Correct Solution:
# Correct: Handles both smaller difference AND tie-breaking if current_difference < min_difference or \ (current_difference == min_difference and node.val < closest_value): min_difference = current_difference closest_value = node.val
2. Early Termination When Finding Exact Match
The Pitfall: Some might optimize by immediately returning when node.val == target
, thinking they've found the perfect match. However, this prevents proper tie-breaking when the target is exactly between two values.
Incorrect Implementation:
def dfs(node):
if node is None:
return
# Wrong: Early return prevents checking other equal-distance nodes
if node.val == target:
return node.val
# ... rest of logic
Why It's Wrong: If target is 2.5
and we encounter node 2
first, then node 3
, we might incorrectly return 3
if we store it as "exact match" without comparing which is smaller.
3. Traversing Both Subtrees Instead of One
The Pitfall: Traversing the entire tree defeats the purpose of using a BST's properties for optimization. This changes complexity from O(h) to O(n).
Incorrect Implementation:
def dfs(node):
if node is None:
return
# Update closest value logic...
# Wrong: Explores both subtrees unnecessarily
dfs(node.left)
dfs(node.right)
Correct Approach: The solution should choose only one direction based on the comparison between target and current node value, ensuring O(h) complexity while still checking all nodes along the chosen path.
4. Integer Division or Rounding Issues
The Pitfall: Using integer operations or rounding when dealing with the float target can cause precision loss.
Incorrect Implementation:
# Wrong: Converting target to int loses precision
target_int = int(target)
current_difference = abs(target_int - node.val)
Correct Solution: Always maintain the target as a float and use abs(target - node.val)
to preserve precision in distance calculations.
How would you design a stack which has a function min
that returns the minimum element in the stack, in addition to push
and pop
? All push
, pop
, min
should have running time O(1)
.
Recommended Readings
Everything About Trees A tree is a type of graph data structure composed of nodes and edges Its main properties are It is acyclic doesn't contain any cycles There exists a path from the root to any node Has N 1 edges where N is the number of nodes in the tree and
https assets algo monster cover_photos dfs svg Depth First Search Prereqs Recursion Review problems recursion_intro Trees problems tree_intro With a solid understanding of recursion under our belts we are now ready to tackle one of the most useful techniques in coding interviews Depth First Search DFS As the name suggests
Binary Search Tree Intro Definition A Binary Search Tree or BST is a rooted binary tree with the value of each internal node being greater than all the values in the respective node's left subtree and less than the ones in its right subtree An empty tree is a BST since it satisfies the
Want a Structured Path to Master System Design Too? Don’t Miss This!