Facebook Pixel

116. Populating Next Right Pointers in Each Node

Problem Description

You are given a perfect binary tree where all leaves are at the same level and every parent node has exactly two children. Each node in the tree has the following structure:

struct Node {
  int val;
  Node *left;
  Node *right;
  Node *next;
}

Your task is to populate each next pointer to point to its next right node on the same level. If there is no next right node (i.e., the node is the rightmost node on that level), the next pointer should be set to NULL.

Initially, all next pointers are set to NULL.

For example, if you have a perfect binary tree:

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

After connecting the next pointers, it should look like:

        1 -> NULL
       / \
      2->3 -> NULL
     / \ / \
    4->5->6->7 -> NULL

The connections are made horizontally across each level, linking nodes from left to right. The rightmost node at each level points to NULL.

The solution uses a Breadth-First Search (BFS) approach with a queue to traverse the tree level by level. For each level, it processes all nodes from left to right, connecting each node to the next node in the queue at the same level. The algorithm maintains a pointer p to track the previous node and sets p.next to the current node, effectively creating the horizontal connections.

Flowchart Walkthrough

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

Is it a graph?

  • Yes: The problem involves a binary tree, which is a special type of graph with nodes (tree nodes) and edges (parent-child relationships).

Is it a tree?

  • Yes: The problem explicitly states we're working with a perfect binary tree where each node has connections to its children.

DFS

  • Yes: We arrive at DFS as our algorithm choice for tree problems.

Conclusion: The flowchart suggests using a Depth-First Search (DFS) approach for this tree problem.

Why DFS Works Here

While the provided solution uses BFS (level-order traversal), DFS is equally valid and can solve this problem efficiently. With DFS, we can:

  1. Recursive approach: Process the tree depth-first while maintaining references to connect nodes at the same level
  2. Use parent pointers: Since it's a perfect binary tree, we can leverage the parent's next pointer to connect children nodes
  3. Space efficiency: DFS can achieve O(1) extra space (excluding recursion stack) compared to BFS's O(w) where w is the width of the tree

A DFS solution would traverse down the tree, and at each level:

  • Connect the left child to the right child
  • If the parent has a next pointer, connect the parent's right child to the parent's next node's left child
  • Recursively process both subtrees

This demonstrates that tree problems often have multiple valid approaches, and the flowchart correctly identifies DFS as a suitable pattern for tree-based problems.

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

Intuition

The key insight for this problem comes from understanding what we're trying to achieve: we need to connect nodes horizontally across each level of the tree, essentially creating "linked lists" at each depth.

Think about how you would manually connect these nodes if you were drawing the tree on paper. You'd naturally process the tree level by level, connecting each node to its right neighbor. This immediately suggests a level-order traversal pattern.

Why does BFS naturally fit here? When we use a queue for BFS, at any given moment, the queue contains all nodes at the current level. This is perfect because:

  1. We can process all nodes at the same level together
  2. We know exactly how many nodes are at each level (the queue size at the start of processing that level)
  3. We can connect them sequentially as we dequeue them

The elegant part of the solution is the use of a temporary pointer p that tracks the "previous" node we just processed. As we dequeue each node:

  • If p exists (not the first node in the level), we set p.next to the current node
  • We update p to point to the current node
  • We add the node's children to the queue for the next level

This creates a chain: node1 -> node2 -> node3 -> ... -> NULL for each level.

While DFS could also solve this (by using the parent's next pointer to find cousins), BFS is more intuitive here because the problem is fundamentally about connecting nodes at the same level - and BFS naturally groups nodes by level. It's like reading the tree line by line, left to right, which matches exactly what we need to do.

Solution Approach

The solution implements a Breadth-First Search (BFS) using a queue to traverse the tree level by level. Here's how the implementation works:

1. Base Case Check:

if root is None:
    return root

If the tree is empty, we simply return None.

2. Initialize the Queue:

q = deque([root])

We start with a queue containing only the root node. The deque (double-ended queue) provides efficient O(1) operations for both adding and removing elements.

3. Level-by-Level Processing:

while q:
    p = None
    for _ in range(len(q)):

The outer while loop continues as long as there are nodes to process. For each level, we:

  • Initialize p to None - this will track the previous node in the current level
  • Use range(len(q)) to process exactly the number of nodes at the current level

4. Connecting Nodes:

node = q.popleft()
if p:
    p.next = node
p = node

For each node in the current level:

  • We dequeue it from the left of the queue
  • If p is not None (meaning this isn't the first node in the level), we connect the previous node to the current node via p.next = node
  • We update p to point to the current node for the next iteration

5. Adding Children for Next Level:

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

We add the children of the current node to the queue. These will be processed in the next level iteration.

Key Pattern: The algorithm ensures that when we start processing a level, the queue contains exactly all nodes of that level and nothing else. As we process these nodes, we're simultaneously:

  • Connecting them horizontally (via the next pointers)
  • Preparing the next level (by adding their children to the queue)

Time Complexity: O(n) where n is the number of nodes, as we visit each node exactly once.

Space Complexity: O(w) where w is the maximum width of the tree. For a perfect binary tree, this is O(n/2) = O(n) for the last level.

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 small perfect binary tree to see how the BFS solution connects the nodes:

Initial tree:
      1
     / \
    2   3

Step 1: Initialize

  • Queue: [1]
  • Start processing level 1

Step 2: Process Level 1 (root)

  • Queue size = 1, so we'll process 1 node
  • Dequeue node 1, p = None
  • Since p is None, we don't set any next pointer
  • Update p = 1
  • Add children: Queue becomes [2, 3]
  • Level 1 complete: Node 1.next remains NULL

Step 3: Process Level 2

  • Queue size = 2, so we'll process 2 nodes
  • Initialize p = None for this level

First node of level 2:

  • Dequeue node 2, current p = None
  • Since p is None, we don't set any next pointer
  • Update p = 2
  • No children to add (leaf node)

Second node of level 2:

  • Dequeue node 3, current p = 2
  • Since p is not None, set p.next = 3 (so 2.next = 3)
  • Update p = 3
  • No children to add (leaf node)

Step 4: Complete

  • Queue is empty, algorithm terminates

Final Result:

      1 -> NULL
     / \
    2->3 -> NULL

The key insight: At each level, the first node doesn't get a next pointer set (since p starts as None), but every subsequent node gets connected to by the previous node. The rightmost node naturally points to NULL since no node sets its next pointer.

Solution Implementation

1"""
2# Definition for a Node.
3class Node:
4    def __init__(self, val: int = 0, left: 'Node' = None, right: 'Node' = None, next: 'Node' = None):
5        self.val = val
6        self.left = left
7        self.right = right
8        self.next = next
9"""
10
11from collections import deque
12from typing import Optional
13
14
15class Solution:
16    def connect(self, root: Optional['Node']) -> Optional['Node']:
17        """
18        Connects each node to its next right node in the same level.
19        Uses BFS (level-order traversal) to process nodes level by level.
20      
21        Args:
22            root: The root node of the binary tree
23          
24        Returns:
25            The root node with all next pointers connected
26        """
27        # Handle empty tree case
28        if root is None:
29            return root
30      
31        # Initialize queue with root node for BFS traversal
32        queue = deque([root])
33      
34        # Process tree level by level
35        while queue:
36            # Store the previous node in the current level
37            previous_node = None
38            # Get the current level size
39            level_size = len(queue)
40          
41            # Process all nodes in the current level
42            for _ in range(level_size):
43                # Get the next node from the queue
44                current_node = queue.popleft()
45              
46                # Connect previous node to current node if previous exists
47                if previous_node:
48                    previous_node.next = current_node
49              
50                # Update previous node for next iteration
51                previous_node = current_node
52              
53                # Add children to queue for next level processing
54                if current_node.left:
55                    queue.append(current_node.left)
56                if current_node.right:
57                    queue.append(current_node.right)
58      
59        return root
60
1/*
2// Definition for a Node.
3class Node {
4    public int val;
5    public Node left;
6    public Node right;
7    public Node next;
8
9    public Node() {}
10
11    public Node(int _val) {
12        val = _val;
13    }
14
15    public Node(int _val, Node _left, Node _right, Node _next) {
16        val = _val;
17        left = _left;
18        right = _right;
19        next = _next;
20    }
21};
22*/
23
24class Solution {
25    /**
26     * Connects each node to its next right node in the same level.
27     * If there is no next right node, the next pointer is set to NULL.
28     * Uses level-order traversal (BFS) approach.
29     * 
30     * @param root The root node of the binary tree
31     * @return The root node with next pointers populated
32     */
33    public Node connect(Node root) {
34        // Handle empty tree
35        if (root == null) {
36            return root;
37        }
38      
39        // Initialize queue for level-order traversal
40        Deque<Node> queue = new ArrayDeque<>();
41        queue.offer(root);
42      
43        // Process each level of the tree
44        while (!queue.isEmpty()) {
45            // Track the previous node in the current level
46            Node previousNode = null;
47          
48            // Get the number of nodes in the current level
49            int levelSize = queue.size();
50          
51            // Process all nodes in the current level
52            for (int i = 0; i < levelSize; i++) {
53                // Get the current node from the queue
54                Node currentNode = queue.poll();
55              
56                // Connect the previous node to the current node
57                if (previousNode != null) {
58                    previousNode.next = currentNode;
59                }
60              
61                // Update previous node for the next iteration
62                previousNode = currentNode;
63              
64                // Add left child to queue for next level processing
65                if (currentNode.left != null) {
66                    queue.offer(currentNode.left);
67                }
68              
69                // Add right child to queue for next level processing
70                if (currentNode.right != null) {
71                    queue.offer(currentNode.right);
72                }
73            }
74        }
75      
76        return root;
77    }
78}
79
1/*
2// Definition for a Node.
3class Node {
4public:
5    int val;
6    Node* left;
7    Node* right;
8    Node* next;
9
10    Node() : val(0), left(NULL), right(NULL), next(NULL) {}
11
12    Node(int _val) : val(_val), left(NULL), right(NULL), next(NULL) {}
13
14    Node(int _val, Node* _left, Node* _right, Node* _next)
15        : val(_val), left(_left), right(_right), next(_next) {}
16};
17*/
18
19class Solution {
20public:
21    Node* connect(Node* root) {
22        // Handle empty tree case
23        if (!root) {
24            return root;
25        }
26      
27        // Initialize queue for level-order traversal
28        queue<Node*> nodeQueue;
29        nodeQueue.push(root);
30      
31        // Process tree level by level
32        while (!nodeQueue.empty()) {
33            // Track the previous node in the current level
34            Node* previousNode = nullptr;
35          
36            // Get the number of nodes in the current level
37            int levelSize = nodeQueue.size();
38          
39            // Process all nodes in the current level
40            for (int i = 0; i < levelSize; ++i) {
41                // Get the current node from the queue
42                Node* currentNode = nodeQueue.front();
43                nodeQueue.pop();
44              
45                // Connect the previous node to the current node
46                if (previousNode) {
47                    previousNode->next = currentNode;
48                }
49              
50                // Update the previous node for the next iteration
51                previousNode = currentNode;
52              
53                // Add left child to queue if it exists
54                if (currentNode->left) {
55                    nodeQueue.push(currentNode->left);
56                }
57              
58                // Add right child to queue if it exists
59                if (currentNode->right) {
60                    nodeQueue.push(currentNode->right);
61                }
62            }
63            // Note: The last node in each level automatically has next = nullptr
64        }
65      
66        return root;
67    }
68};
69
1/**
2 * Definition for Node.
3 * class Node {
4 *     val: number
5 *     left: Node | null
6 *     right: Node | null
7 *     next: Node | null
8 *     constructor(val?: number, left?: Node, right?: Node, next?: Node) {
9 *         this.val = (val===undefined ? 0 : val)
10 *         this.left = (left===undefined ? null : left)
11 *         this.right = (right===undefined ? null : right)
12 *         this.next = (next===undefined ? null : next)
13 *     }
14 * }
15 */
16
17/**
18 * Connects each node to its next right node in a perfect binary tree.
19 * The next pointer should point to the next node on the same level from left to right.
20 * 
21 * @param root - The root node of the perfect binary tree
22 * @returns The root of the modified tree with next pointers connected
23 */
24function connect(root: Node | null): Node | null {
25    // Base case: if root is null or has no children, return as is
26    if (root === null || root.left === null) {
27        return root;
28    }
29  
30    // Destructure the root node's properties for cleaner access
31    const { left, right, next } = root;
32  
33    // Connect the left child to the right child
34    left.next = right;
35  
36    // If the current node has a next sibling, connect right child to sibling's left child
37    if (next !== null) {
38        right.next = next.left;
39    }
40  
41    // Recursively process the left subtree
42    connect(left);
43  
44    // Recursively process the right subtree
45    connect(right);
46  
47    // Return the modified root
48    return root;
49}
50

Time and Space Complexity

The time complexity is O(n), where n is the number of nodes in the binary tree. This is because the algorithm uses a breadth-first search (BFS) approach with a queue, visiting each node exactly once. Each node is enqueued once and dequeued once, and the operations performed on each node (setting the next pointer, checking children) take constant time O(1).

The space complexity is O(n), where n is the number of nodes in the binary tree. The space is dominated by the queue q used for BFS traversal. In the worst case, the queue needs to store all nodes at the maximum width level of the tree. For a complete binary tree, the last level can contain up to n/2 nodes, making the space complexity O(n). Additionally, the variable p only uses O(1) extra space, which doesn't affect the overall space complexity.

Common Pitfalls

1. Not Preserving Level Boundaries

A frequent mistake is failing to process nodes strictly level by level, which results in incorrect connections between nodes from different levels.

Incorrect Implementation:

# WRONG: This connects nodes across levels
while queue:
    node = queue.popleft()
    if queue:  # This might connect to a node from the next level!
        node.next = queue[0]
    if node.left:
        queue.append(node.left)
    if node.right:
        queue.append(node.right)

Why it fails: When processing the last node of a level, the queue already contains nodes from the next level. Setting node.next = queue[0] would incorrectly connect a node to its child or nephew instead of NULL.

Correct Approach:

while queue:
    level_size = len(queue)  # Capture current level size
    for i in range(level_size):  # Process exactly this many nodes
        node = queue.popleft()
        # Only connect if not the last node in the level
        if i < level_size - 1:
            node.next = queue[0]  # Safe: queue[0] is in same level
        # Add children for next level
        if node.left:
            queue.append(node.left)
        if node.right:
            queue.append(node.right)

2. Inefficient Space Usage for Perfect Binary Trees

Since the problem guarantees a perfect binary tree, you can optimize space by using the already-established next pointers instead of a queue.

Space-Optimized Solution (O(1) space):

def connect(self, root: Optional['Node']) -> Optional['Node']:
    if not root:
        return root
  
    # Start with root level
    leftmost = root
  
    # Process each level using the next pointers from previous level
    while leftmost.left:  # While there's a next level
        # Iterate through current level using next pointers
        head = leftmost
        while head:
            # Connect left child to right child
            head.left.next = head.right
          
            # Connect right child to next node's left child
            if head.next:
                head.right.next = head.next.left
          
            # Move to next node in current level
            head = head.next
      
        # Move to next level
        leftmost = leftmost.left
  
    return root

3. Forgetting to Handle Edge Cases

While the BFS solution naturally handles most edge cases, developers sometimes forget to check for an empty tree or make assumptions about tree structure.

Common Oversight:

# WRONG: Assumes tree is never empty
def connect(self, root):
    queue = deque([root])  # Will process None if root is None!
    while queue:
        # Processing None will cause AttributeError
        node = queue.popleft()
        # ...

Proper Handling:

def connect(self, root):
    if not root:  # Always check for empty tree
        return root
    queue = deque([root])
    # ... rest of the logic

4. Misunderstanding the Problem Requirements

Some developers mistakenly try to connect nodes vertically (parent to child) or diagonally instead of horizontally within the same level.

Remember: The next pointer should only connect nodes that are:

  • On the same level (same distance from root)
  • Adjacent in left-to-right order
  • The rightmost node of each level should have next = NULL
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Is the following code DFS or BFS?

void search(Node root) {
  if (!root) return;
  visit(root);
  root.visited = true;
  for (Node node in root.adjacent) {
    if (!node.visited) {
      search(node);
    }
  }
}

Recommended Readings

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

Load More