Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

742. Closest Leaf in a Binary Tree - Leetcode Solution

Code Implementation

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

from collections import deque, defaultdict

class Solution:
    def findClosestLeaf(self, root: TreeNode, k: int) -> int:
        # Step 1: Build graph (undirected) and find the target node
        graph = defaultdict(list)
        target = None

        def dfs(node, parent):
            nonlocal target
            if not node:
                return
            if node.val == k:
                target = node
            if parent:
                graph[node].append(parent)
                graph[parent].append(node)
            if node.left:
                dfs(node.left, node)
            if node.right:
                dfs(node.right, node)
        
        dfs(root, None)

        # Step 2: BFS from target node to find the closest leaf
        queue = deque()
        queue.append(target)
        visited = set()
        visited.add(target)
        while queue:
            node = queue.popleft()
            if not node.left and not node.right:
                return node.val
            for neighbor in graph[node]:
                if neighbor not in visited:
                    visited.add(neighbor)
                    queue.append(neighbor)
      
// Definition for a binary tree node.
// struct TreeNode {
//     int val;
//     TreeNode *left;
//     TreeNode *right;
//     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
// };

#include <unordered_map>
#include <unordered_set>
#include <queue>
#include <vector>

class Solution {
public:
    int findClosestLeaf(TreeNode* root, int k) {
        std::unordered_map<TreeNode*, std::vector<TreeNode*>> graph;
        TreeNode* target = nullptr;

        // DFS to build undirected graph and find target node
        std::function<void(TreeNode*, TreeNode*)> dfs = [&](TreeNode* node, TreeNode* parent) {
            if (!node) return;
            if (node->val == k) target = node;
            if (parent) {
                graph[node].push_back(parent);
                graph[parent].push_back(node);
            }
            if (node->left) dfs(node->left, node);
            if (node->right) dfs(node->right, node);
        };

        dfs(root, nullptr);

        // BFS from target
        std::queue<TreeNode*> q;
        std::unordered_set<TreeNode*> visited;
        q.push(target);
        visited.insert(target);

        while (!q.empty()) {
            TreeNode* node = q.front(); q.pop();
            if (!node->left && !node->right) {
                return node->val;
            }
            for (TreeNode* neighbor : graph[node]) {
                if (!visited.count(neighbor)) {
                    visited.insert(neighbor);
                    q.push(neighbor);
                }
            }
        }
        return -1; // Should not reach here based on problem constraints
    }
};
      
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
import java.util.*;

class Solution {
    public int findClosestLeaf(TreeNode root, int k) {
        Map<TreeNode, List<TreeNode>> graph = new HashMap<>();
        TreeNode[] target = new TreeNode[1];

        // DFS to build graph and find target
        buildGraph(root, null, k, graph, target);

        // BFS from target
        Queue<TreeNode> queue = new LinkedList<>();
        Set<TreeNode> visited = new HashSet<>();
        queue.offer(target[0]);
        visited.add(target[0]);

        while (!queue.isEmpty()) {
            TreeNode node = queue.poll();
            if (node.left == null && node.right == null) {
                return node.val;
            }
            for (TreeNode neighbor : graph.getOrDefault(node, new ArrayList<>())) {
                if (!visited.contains(neighbor)) {
                    visited.add(neighbor);
                    queue.offer(neighbor);
                }
            }
        }
        return -1;
    }

    private void buildGraph(TreeNode node, TreeNode parent, int k, Map<TreeNode, List<TreeNode>> graph, TreeNode[] target) {
        if (node == null) return;
        if (node.val == k) target[0] = node;
        if (parent != null) {
            graph.computeIfAbsent(node, x -> new ArrayList<>()).add(parent);
            graph.computeIfAbsent(parent, x -> new ArrayList<>()).add(node);
        }
        buildGraph(node.left, node, k, graph, target);
        buildGraph(node.right, node, k, graph, target);
    }
}
      
/**
 * Definition for a binary tree node.
 * function TreeNode(val) {
 *     this.val = val;
 *     this.left = this.right = null;
 * }
 */

/**
 * @param {TreeNode} root
 * @param {number} k
 * @return {number}
 */
var findClosestLeaf = function(root, k) {
    const graph = new Map();
    let target = null;

    function dfs(node, parent) {
        if (!node) return;
        if (node.val === k) target = node;
        if (parent) {
            if (!graph.has(node)) graph.set(node, []);
            if (!graph.has(parent)) graph.set(parent, []);
            graph.get(node).push(parent);
            graph.get(parent).push(node);
        }
        if (node.left) dfs(node.left, node);
        if (node.right) dfs(node.right, node);
    }

    dfs(root, null);

    // BFS from target
    const queue = [target];
    const visited = new Set([target]);
    while (queue.length) {
        const node = queue.shift();
        if (!node.left && !node.right) {
            return node.val;
        }
        for (const neighbor of (graph.get(node) || [])) {
            if (!visited.has(neighbor)) {
                visited.add(neighbor);
                queue.push(neighbor);
            }
        }
    }
    return -1;
};
      

Problem Description

Given the root of a binary tree and an integer k representing the value of a target node in the tree, find the value of the closest leaf node to the target node. The closest leaf is defined as the leaf node that can be reached from the target node with the minimum number of edges. The tree is guaranteed to have at least one node, and all node values are unique. There is exactly one valid solution for each input.

  • A leaf node is a node with no left or right children.
  • You must not reuse elements; the path must be simple and cannot revisit nodes.
  • Input: root (the root of the tree), k (the target node's value).
  • Output: The value of the closest leaf node to the node with value k.

Thought Process

At first glance, this problem appears similar to finding the shortest path in a tree. A brute-force approach might be to traverse from the root to every leaf, recording the path, and, for each path, check if the target node is present. If it is, calculate the distance from the target to the leaf. However, this would be inefficient, especially for large trees.

The key insight is that, since the tree is undirected in terms of "distance" (you can move up to parents and down to children), we can model the tree as an undirected graph. This allows us to use Breadth-First Search (BFS) starting from the target node, guaranteeing that the first leaf we reach is the closest.

Thus, we need to:

  • Find the target node.
  • Allow traversal both up (to parent) and down (to children).
  • Use BFS to find the nearest leaf efficiently.
This shift from a purely downward tree traversal to a graph-based approach is the main optimization.

Solution Approach

Let's break down the solution into clear steps:

  1. Build an Undirected Graph Representation:
    • We traverse the tree and for each node, we connect it to its parent and its children bidirectionally.
    • This allows us to move both up and down from any node during our search.
    • While building the graph, we also locate the reference to the target node with value k.
  2. Breadth-First Search (BFS) from Target Node:
    • We initialize a queue with the target node and a set to keep track of visited nodes.
    • At each step, we dequeue a node and check if it's a leaf (no left or right child).
    • If it is, we return its value—since BFS guarantees this is the closest leaf.
    • Otherwise, we enqueue all unvisited neighbors (parent and children).

Why this works: By turning the tree into a graph, we ensure that the shortest path to any leaf can be found efficiently using BFS, just like finding the shortest path in an unweighted graph.

Example Walkthrough

Example Tree:

        1
       / \
      2   3
     /
    4
  

Let's say k = 2. The node with value 2 has a left child (4) and a parent (1).

  • We build the graph: 1 connects to 2 and 3; 2 connects to 1 and 4; 3 connects to 1; 4 connects to 2.
  • Starting BFS from node 2:
    • Step 1: Node 2. Not a leaf (has child).
    • Step 2: Visit neighbors 1 and 4.
    • Node 1: Not a leaf (has children).
    • Node 4: Is a leaf (no children). Return 4.
The closest leaf to node 2 is 4 (distance 1).

Time and Space Complexity

Brute-Force Approach:

  • Traversing all root-to-leaf paths and checking for the target node would require O(N^2) time in the worst case (for N nodes), since each path could be up to N in length.
  • Space complexity would also be high due to storing all paths.
Optimized (Graph + BFS) Approach:
  • Building the graph takes O(N) time and space (every node and edge is visited once).
  • BFS traverses each node at most once, also O(N) time and space.
  • Overall: O(N) time and O(N) space.

This efficiency is achieved because each node and edge is processed only a constant number of times.

Summary

This problem is elegantly solved by transforming the binary tree into an undirected graph, allowing for efficient BFS from the target node to the nearest leaf. The main insight is realizing that the shortest path may go upwards as well as downwards in the tree. By leveraging graph traversal techniques, we achieve a linear-time solution that is both simple and robust.