Facebook Pixel

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 is 3.714286, the closest value would be 4 since |4 - 3.714286| = 0.285714 is the smallest difference.
  • If the target is exactly between two values, like 3.5 with nodes 3 and 4 both having a distance of 0.5, you would return 3 (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:

  1. We need to traverse the tree to examine node values and compare them with the target
  2. DFS allows us to efficiently navigate the BST by leveraging its ordering property (left subtree has smaller values, right subtree has larger values)
  3. 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
  4. 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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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:

  1. At each node, calculate the absolute difference |node.val - target|
  2. Keep track of the node with the minimum difference seen so far
  3. If there's a tie (same difference), choose the smaller value
  4. Continue searching in the direction where closer values might exist
  5. 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 target
  • diff: stores the minimum absolute difference found so far (initialized to infinity)

Step-by-step Algorithm:

  1. Base Case: If the current node is None, we return immediately (end of path reached).

  2. Calculate Distance: For the current node, we calculate the absolute difference between its value and the target:

    nxt = abs(target - node.val)
  3. Update Answer: We update our answer in two cases:

    • If the current difference nxt is smaller than the best difference diff 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
  4. 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).

  5. 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 keep 5 as the answer
  • If we find 4.5 (difference = 0.1), we update to 4.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 Evaluator

Example 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 is None

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 return 4

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.

Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

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

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More