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:
- Recursive approach: Process the tree depth-first while maintaining references to connect nodes at the same level
- Use parent pointers: Since it's a perfect binary tree, we can leverage the parent's
next
pointer to connect children nodes - 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.
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:
- We can process all nodes at the same level together
- We know exactly how many nodes are at each level (the queue size at the start of processing that level)
- 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 setp.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
toNone
- 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 notNone
(meaning this isn't the first node in the level), we connect the previous node to the current node viap.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 EvaluatorExample 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
isNone
, we don't set anynext
pointer - Update
p = 1
- Add children: Queue becomes
[2, 3]
- Level 1 complete: Node
1.next
remainsNULL
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
, currentp = None
- Since
p
isNone
, we don't set anynext
pointer - Update
p = 2
- No children to add (leaf node)
Second node of level 2:
- Dequeue node
3
, currentp = 2
- Since
p
is notNone
, setp.next = 3
(so2.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
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
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
https assets algo monster cover_photos bfs svg Breadth First Search on Trees Hopefully by this time you've drunk enough DFS Kool Aid to understand its immense power and seen enough visualization to create a call stack in your mind Now let me introduce the companion spell Breadth First Search BFS
Want a Structured Path to Master System Design Too? Don’t Miss This!