Facebook Pixel

1602. Find Nearest Right Node in Binary Tree 🔒

Problem Description

You are given a binary tree with a root node and a specific node u that exists somewhere in the tree. Your task is to find the node that is immediately to the right of node u on the same level (depth) in the tree.

The key requirements are:

  • The target node must be on the same level as node u (same distance from the root)
  • The target node must be the nearest node to the right of u on that level
  • If u is the rightmost node on its level, return null

For example, if you have a binary tree where nodes on level 3 from left to right are [A, B, u, C, D], and u is the given node, then the answer would be node C since it's the immediate right neighbor of u on the same level.

The solution uses a level-order traversal (BFS) approach with a queue. It processes nodes level by level, and when it encounters node u, it checks if there are more nodes remaining in the current level. If yes, the next node in the queue is the answer; if no (meaning u is the last node in its level), it returns null.

The clever part of the implementation is using a reverse loop range(len(q) - 1, -1, -1) to track how many nodes remain in the current level. When i reaches 0, it means u is the rightmost node of its level.

Flowchart Walkthrough

First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:

Is it a graph?

  • Yes: A binary tree is a special type of graph with nodes and edges (parent-child relationships).

Is it a tree?

  • Yes: The problem explicitly states we're working with a binary tree.

DFS?

  • Not necessarily: While DFS could work, let's reconsider. The problem asks for finding a node on the same level as node u. This is a strong hint that we need level-order traversal, which is characteristic of BFS rather than DFS.

Let me re-evaluate from the graph node:

Is it a graph?

  • Yes: A binary tree is a graph.

Is it a tree?

  • No (taking alternate path): Although it's technically a tree, the problem's requirement to find nodes on the same level suggests we should explore other graph algorithms.

Is the problem related to directed acyclic graphs?

  • No: While a tree is a DAG, the problem isn't about topological ordering.

Is the problem related to shortest paths?

  • No: We're not finding shortest paths; we're finding a specific node on the same level.

Does the problem involve connectivity?

  • No: We're not checking if nodes are connected or finding connected components.

Does the problem have small constraints?

  • No: The problem doesn't specify small constraints, and we need an efficient solution.

BFS?

  • Yes: BFS is perfect for level-order traversal, which naturally processes all nodes at the same depth before moving to the next level.

Conclusion: The flowchart leads us to BFS (Breadth-First Search) as the optimal approach. BFS processes nodes level by level, making it ideal for finding the nearest right node on the same level as node u. When we encounter node u during our level-order traversal, we can immediately check if there's another node in the current level's queue, which would be the answer.

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

Intuition

When we need to find a node that's on the same level as another node, we should think about how to traverse the tree level by level. This immediately points us toward BFS (Breadth-First Search), which naturally processes all nodes at depth 0, then all nodes at depth 1, then depth 2, and so on.

The key insight is that in a standard BFS traversal using a queue, at any given moment during processing of a level, the queue contains exactly the nodes of the current level being processed, followed by nodes of the next level that have been discovered so far.

Here's the clever part: when we're processing nodes level by level, if we find node u, we know that:

  1. If there are still unprocessed nodes in the current level (in the queue), the very next node must be the nearest right neighbor
  2. If u is the last node being processed in this level, then it's the rightmost node and we return null

To track whether we're at the last node of a level, we can process each level completely before moving to the next. We do this by recording the queue size at the start of each level - this tells us exactly how many nodes belong to the current level. As we process each node and decrement our counter, we know precisely when we've reached the last node of the level.

The implementation uses a reverse loop for i in range(len(q) - 1, -1, -1) which counts down from the number of nodes in the current level. When we find u and i > 0, we know there are more nodes in this level, so we return the next node in the queue. When i == 0, node u is the last node in its level, so we return null.

Learn more about Tree, Breadth-First Search and Binary Tree patterns.

Solution Approach

The solution implements a level-order traversal using BFS with a queue data structure. Here's how the algorithm works:

  1. Initialize the queue: Start with a deque containing only the root node: q = deque([root])

  2. Process levels iteratively: The outer while q: loop continues as long as there are nodes to process.

  3. Process each level completely: For each level, we use for i in range(len(q) - 1, -1, -1) to process exactly the nodes that belong to the current level. The key insight is that len(q) at the start of each iteration tells us how many nodes are in the current level.

  4. Check for the target node: For each node processed:

    • Pop the leftmost node from the queue: root = q.popleft()
    • Check if this is our target node u: if root == u:
    • If it is node u:
      • If i > 0, there are still nodes remaining in the current level, so return q[0] (the next node in the queue, which is the nearest right neighbor)
      • If i == 0, node u is the last node in this level, so return None
  5. Add children for next level: After checking each node, add its children to the queue for processing in the next level:

    if root.left:
        q.append(root.left)
    if root.right:
        q.append(root.right)

The algorithm guarantees that when we find node u, all nodes to its left on the same level have already been processed and removed from the queue, while any nodes to its right on the same level are still in the queue. The very first element in the queue at that moment is the nearest right node.

Time Complexity: O(n) where n is the number of nodes in the tree, as we might need to traverse all nodes in the worst case.

Space Complexity: O(w) where w is the maximum width of the tree, which represents the maximum number of nodes at any level stored in the queue.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a concrete example to understand how the algorithm finds the right neighbor of a given node.

Consider this binary tree:

        1
       / \
      2   3
     / \   \
    4   5   6
   /
  7

Task: Find the right neighbor of node u = 5 (which should be node 6).

Step-by-step execution:

Initial state: q = deque([1]) (queue contains only root)

Level 1 (root level):

  • Queue at start: [1], level size = 1
  • Process node 1 (i = 0):
    • Pop node 1 from queue
    • Not our target node u
    • Add children: queue becomes [2, 3]

Level 2:

  • Queue at start: [2, 3], level size = 2
  • Process node 2 (i = 1):
    • Pop node 2 from queue
    • Not our target node u
    • Add children: queue becomes [3, 4, 5]
  • Process node 3 (i = 0):
    • Pop node 3 from queue
    • Not our target node u
    • Add children: queue becomes [4, 5, 6]

Level 3:

  • Queue at start: [4, 5, 6], level size = 3
  • Process node 4 (i = 2):
    • Pop node 4 from queue
    • Not our target node u
    • Add children: queue becomes [5, 6, 7]
  • Process node 5 (i = 1):
    • Pop node 5 from queue
    • This is our target node u!
    • Check: i = 1 > 0, so there are more nodes in this level
    • Return q[0] which is node 6 ✓

Result: The algorithm correctly returns node 6 as the right neighbor of node 5.

Alternative scenario: If we were looking for the right neighbor of node 6 instead:

  • When we reach node 6 at level 3, i would equal 0 (last node in level)
  • The algorithm would return None since node 6 is the rightmost node on its level

The key insight is that the counter i tells us exactly how many nodes remain unprocessed in the current level, allowing us to determine if our target node has a right neighbor or not.

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 collections import deque
9from typing import Optional
10
11class Solution:
12    def findNearestRightNode(self, root: TreeNode, u: TreeNode) -> Optional[TreeNode]:
13        """
14        Find the nearest right node of node u in the same level of a binary tree.
15      
16        Args:
17            root: The root of the binary tree
18            u: The target node to find the right neighbor of
19          
20        Returns:
21            The nearest right node in the same level, or None if it doesn't exist
22        """
23        # Initialize queue with root node for level-order traversal
24        queue = deque([root])
25      
26        # Process tree level by level
27        while queue:
28            # Get the current level size
29            level_size = len(queue)
30          
31            # Process all nodes in the current level
32            for i in range(level_size):
33                # Remove and process the leftmost node in the queue
34                current_node = queue.popleft()
35              
36                # Check if we found the target node
37                if current_node == u:
38                    # If not the last node in level, return the next node (right neighbor)
39                    # Otherwise, return None (no right neighbor exists)
40                    return queue[0] if i < level_size - 1 else None
41              
42                # Add children to queue for next level processing
43                if current_node.left:
44                    queue.append(current_node.left)
45                if current_node.right:
46                    queue.append(current_node.right)
47      
48        # Target node not found in tree
49        return None
50
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    /**
18     * Finds the nearest right node of a given node u in a binary tree.
19     * The nearest right node is defined as the node that is at the same level
20     * and immediately to the right of node u.
21     * 
22     * @param root The root of the binary tree
23     * @param u The target node to find the nearest right neighbor for
24     * @return The nearest right node if it exists, null otherwise
25     */
26    public TreeNode findNearestRightNode(TreeNode root, TreeNode u) {
27        // Initialize queue for level-order traversal (BFS)
28        Deque<TreeNode> queue = new ArrayDeque<>();
29        queue.offer(root);
30      
31        // Process tree level by level
32        while (!queue.isEmpty()) {
33            // Get the number of nodes at current level
34            int levelSize = queue.size();
35          
36            // Process all nodes at current level
37            for (int i = 0; i < levelSize; i++) {
38                TreeNode currentNode = queue.pollFirst();
39              
40                // Check if current node is the target node u
41                if (currentNode == u) {
42                    // If there are more nodes at this level, return the next one
43                    // Otherwise, return null (u is the rightmost node at this level)
44                    return (i < levelSize - 1) ? queue.peekFirst() : null;
45                }
46              
47                // Add children of current node to queue for next level processing
48                if (currentNode.left != null) {
49                    queue.offer(currentNode.left);
50                }
51                if (currentNode.right != null) {
52                    queue.offer(currentNode.right);
53                }
54            }
55        }
56      
57        // Target node u was not found in the tree
58        return null;
59    }
60}
61
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     * Find the nearest node to the right of target node u at the same level in binary tree
16     * @param root: Root of the binary tree
17     * @param u: Target node to find the right neighbor of
18     * @return: The nearest right node at the same level, or nullptr if none exists
19     */
20    TreeNode* findNearestRightNode(TreeNode* root, TreeNode* u) {
21        // Initialize queue for level-order traversal (BFS)
22        std::queue<TreeNode*> nodeQueue;
23        nodeQueue.push(root);
24      
25        // Process tree level by level
26        while (!nodeQueue.empty()) {
27            // Get the number of nodes at current level
28            int levelSize = nodeQueue.size();
29          
30            // Process all nodes at current level
31            for (int i = 0; i < levelSize; i++) {
32                // Get and remove the front node from queue
33                TreeNode* currentNode = nodeQueue.front();
34                nodeQueue.pop();
35              
36                // Check if current node is the target node u
37                if (currentNode == u) {
38                    // If there are more nodes at this level, return the next one
39                    // Otherwise, return nullptr (u is the rightmost node at this level)
40                    return (i < levelSize - 1) ? nodeQueue.front() : nullptr;
41                }
42              
43                // Add children of current node to queue for next level processing
44                if (currentNode->left != nullptr) {
45                    nodeQueue.push(currentNode->left);
46                }
47                if (currentNode->right != nullptr) {
48                    nodeQueue.push(currentNode->right);
49                }
50            }
51        }
52      
53        // Target node u was not found in the tree
54        return nullptr;
55    }
56};
57
1/**
2 * Definition for a binary tree node.
3 */
4interface TreeNode {
5    val: number;
6    left: TreeNode | null;
7    right: TreeNode | null;
8}
9
10/**
11 * Finds the nearest node to the right of the target node u on the same level
12 * Uses BFS (Breadth-First Search) to traverse the tree level by level
13 * 
14 * @param root - The root node of the binary tree
15 * @param u - The target node to find the right neighbor for
16 * @returns The nearest right node on the same level, or null if none exists
17 */
18const findNearestRightNode = function(root: TreeNode | null, u: TreeNode | null): TreeNode | null {
19    // Edge case: if root is null, return null
20    if (!root || !u) {
21        return null;
22    }
23  
24    // Initialize queue for BFS traversal
25    const queue: TreeNode[] = [root];
26  
27    // Process nodes level by level
28    while (queue.length > 0) {
29        // Store the current level size to process all nodes at this level
30        const levelSize = queue.length;
31      
32        // Process all nodes at the current level
33        for (let i = levelSize; i > 0; i--) {
34            // Dequeue the next node from the front
35            const currentNode = queue.shift()!;
36          
37            // Check if we found the target node u
38            if (currentNode === u) {
39                // If there are more nodes at this level (i > 1), 
40                // the next node in queue is the right neighbor
41                // Otherwise, u is the rightmost node at this level
42                return i > 1 ? queue[0] : null;
43            }
44          
45            // Add children to queue for next level processing
46            if (currentNode.left) {
47                queue.push(currentNode.left);
48            }
49            if (currentNode.right) {
50                queue.push(currentNode.right);
51            }
52        }
53    }
54  
55    // Target node u was not found in the tree
56    return null;
57};
58

Time and Space Complexity

Time Complexity: O(n)

The algorithm uses BFS (Breadth-First Search) to traverse the binary tree level by level. In the worst case, we need to visit every node in the tree exactly once before finding the target node u or determining its nearest right node. Each node is processed once - dequeued from the queue, checked against u, and its children are enqueued. Therefore, the time complexity is O(n), where n is the number of nodes in the binary tree.

Space Complexity: O(n)

The space complexity is determined by the maximum size of the queue q during execution. In the worst case, the queue needs to store all nodes at the widest level of the tree. For a complete binary tree, the last level contains approximately n/2 nodes, making the space complexity O(n). Additionally, the algorithm doesn't use any other data structures that scale with input size, so the overall space complexity remains O(n), where n is the number of nodes in the binary tree.

Learn more about how to find time and space complexity quickly.

Common Pitfalls

Pitfall 1: Confusing Node Position with Queue Position

The Problem: A common mistake is assuming that after finding node u, the next node in the queue is always the right neighbor. This fails when u is the last node of its level because the queue would then contain nodes from the next level.

Incorrect Implementation:

def findNearestRightNode(self, root: TreeNode, u: TreeNode) -> Optional[TreeNode]:
    queue = deque([root])
  
    while queue:
        current = queue.popleft()
      
        if current == u:
            # WRONG: This could return a node from the next level!
            return queue[0] if queue else None
          
        if current.left:
            queue.append(current.left)
        if current.right:
            queue.append(current.right)
  
    return None

The Solution: Track level boundaries explicitly by processing exactly level_size nodes per iteration:

while queue:
    level_size = len(queue)  # Capture current level size
  
    for i in range(level_size):  # Process only this level's nodes
        current = queue.popleft()
      
        if current == u:
            # Check if more nodes exist in THIS level
            return queue[0] if i < level_size - 1 else None
      
        # Add children for next level
        if current.left:
            queue.append(current.left)
        if current.right:
            queue.append(current.right)

Pitfall 2: Not Handling Edge Cases Properly

The Problem: Forgetting to handle special cases like:

  • When u is the root node (no right neighbor possible at level 0)
  • When u doesn't exist in the tree
  • When the tree is empty or None

Incomplete Implementation:

def findNearestRightNode(self, root: TreeNode, u: TreeNode) -> Optional[TreeNode]:
    # Missing null check for root
    queue = deque([root])  # Crashes if root is None
  
    while queue:
        # ... rest of code

The Solution: Add proper validation:

def findNearestRightNode(self, root: TreeNode, u: TreeNode) -> Optional[TreeNode]:
    # Handle edge cases
    if not root or not u:
        return None
  
    # Special case: if u is root, it has no right neighbor at its level
    if root == u:
        return None
  
    queue = deque([root])
    # ... rest of implementation

Pitfall 3: Using Wrong Loop Index Check

The Problem: Using the wrong comparison when checking if there are more nodes in the current level. The original solution description mentions using range(len(q) - 1, -1, -1) and checking i > 0, but this is actually more complex than needed.

Confusing Implementation:

for i in range(len(queue) - 1, -1, -1):  # Reverse iteration
    current = queue.popleft()
  
    if current == u:
        return queue[0] if i > 0 else None  # Confusing: why i > 0?

The Solution: Use forward iteration with clear indexing:

level_size = len(queue)

for i in range(level_size):  # Forward iteration: 0, 1, 2, ...
    current = queue.popleft()
  
    if current == u:
        # Clear check: am I the last node in this level?
        return queue[0] if i < level_size - 1 else None

This makes it immediately clear that we're checking if the current position i is less than the last position level_size - 1.

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

What's the output of running the following function using input [30, 20, 10, 100, 33, 12]?

1def fun(arr: List[int]) -> List[int]:
2    import heapq
3    heapq.heapify(arr)
4    res = []
5    for i in range(3):
6        res.append(heapq.heappop(arr))
7    return res
8
1public static int[] fun(int[] arr) {
2    int[] res = new int[3];
3    PriorityQueue<Integer> heap = new PriorityQueue<>();
4    for (int i = 0; i < arr.length; i++) {
5        heap.add(arr[i]);
6    }
7    for (int i = 0; i < 3; i++) {
8        res[i] = heap.poll();
9    }
10    return res;
11}
12
1class HeapItem {
2    constructor(item, priority = item) {
3        this.item = item;
4        this.priority = priority;
5    }
6}
7
8class MinHeap {
9    constructor() {
10        this.heap = [];
11    }
12
13    push(node) {
14        // insert the new node at the end of the heap array
15        this.heap.push(node);
16        // find the correct position for the new node
17        this.bubble_up();
18    }
19
20    bubble_up() {
21        let index = this.heap.length - 1;
22
23        while (index > 0) {
24            const element = this.heap[index];
25            const parentIndex = Math.floor((index - 1) / 2);
26            const parent = this.heap[parentIndex];
27
28            if (parent.priority <= element.priority) break;
29            // if the parent is bigger than the child then swap the parent and child
30            this.heap[index] = parent;
31            this.heap[parentIndex] = element;
32            index = parentIndex;
33        }
34    }
35
36    pop() {
37        const min = this.heap[0];
38        this.heap[0] = this.heap[this.size() - 1];
39        this.heap.pop();
40        this.bubble_down();
41        return min;
42    }
43
44    bubble_down() {
45        let index = 0;
46        let min = index;
47        const n = this.heap.length;
48
49        while (index < n) {
50            const left = 2 * index + 1;
51            const right = left + 1;
52
53            if (left < n && this.heap[left].priority < this.heap[min].priority) {
54                min = left;
55            }
56            if (right < n && this.heap[right].priority < this.heap[min].priority) {
57                min = right;
58            }
59            if (min === index) break;
60            [this.heap[min], this.heap[index]] = [this.heap[index], this.heap[min]];
61            index = min;
62        }
63    }
64
65    peek() {
66        return this.heap[0];
67    }
68
69    size() {
70        return this.heap.length;
71    }
72}
73
74function fun(arr) {
75    const heap = new MinHeap();
76    for (const x of arr) {
77        heap.push(new HeapItem(x));
78    }
79    const res = [];
80    for (let i = 0; i < 3; i++) {
81        res.push(heap.pop().item);
82    }
83    return res;
84}
85

Recommended Readings

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

Load More