117. Populating Next Right Pointers in Each Node II
Problem Description
You are given a binary tree where each node has the following structure:
val
: an integer valueleft
: pointer to the left childright
: pointer to the right childnext
: pointer that initially points toNULL
Your task is to populate each next
pointer so that it points to the node's next right neighbor on the same level. If there is no next right node on the same level, the next
pointer should remain NULL
.
For example, if you have a binary tree like:
1 / \ 2 3 / \ \ 4 5 7
After connecting the next
pointers, the connections would be:
- Node 1's
next
→NULL
(no node to its right on the same level) - Node 2's
next
→ Node 3 (same level, to the right) - Node 3's
next
→NULL
(no node to its right on the same level) - Node 4's
next
→ Node 5 (same level, to the right) - Node 5's
next
→ Node 7 (same level, to the right) - Node 7's
next
→NULL
(no node to its right on the same level)
The solution uses a level-order traversal (BFS) approach with a queue. For each level of the tree, it processes all nodes from left to right, connecting each node's next
pointer to the subsequent node in the queue at that level. The algorithm maintains a pointer p
to track the previous node processed at the current level, allowing it to set p.next = node
for each new node encountered. This ensures all nodes at the same level are connected via their next
pointers.
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 (tree nodes) and edges (parent-child relationships). Each node can have connections to other nodes through left, right, and next pointers.
Is it a tree?
- Yes: The problem explicitly states we're working with a binary tree structure where each node has left and right children.
DFS
- Yes: We arrive at DFS as a potential solution approach for this tree problem.
Conclusion: The flowchart suggests using a DFS (Depth-First Search) approach for connecting the next right pointers in the binary tree.
While the provided solution uses BFS (level-order traversal), DFS is equally valid for this problem. A DFS approach would involve:
- Traversing the tree depth-first while keeping track of the level
- Maintaining a list or map of the rightmost node seen at each level
- As we visit each node, connecting it to the previously seen node at the same level
- This can be implemented using either recursive DFS or iterative DFS with a stack
The tree structure naturally lends itself to DFS traversal patterns, making it a suitable choice according to the flowchart's decision path.
Intuition
The key insight for this problem is recognizing that we need to connect nodes that are at the same level but not necessarily siblings. When we think about tree traversal, we naturally process nodes level by level or depth by depth.
The most straightforward way to connect nodes on the same level is to process them in the order they appear from left to right. This immediately suggests a level-order traversal pattern. If we can visit all nodes at level 1, then all nodes at level 2, and so on, we can easily link them together as we encounter them.
Think of it like people standing in rows for a group photo - we want each person to hold hands with the person to their right in the same row. The natural way to do this is to go row by row (level by level) and have each person extend their hand to the next person we encounter in that row.
When using BFS with a queue, we have a beautiful property: at any point during the traversal, the queue contains all nodes at the current level (and only those nodes) before we start processing them. This means we can:
- Know exactly how many nodes are at the current level (
len(q)
) - Process them one by one from left to right
- Connect each node to the next one in the queue at that level
The pattern emerges: as we dequeue each node, we can set the previous node's next
pointer to point to the current node. By maintaining a p
pointer that tracks the "previous" node at the current level, we create a chain of connections across each level. When we finish a level, we reset p
to None
for the next level.
This approach naturally handles all cases - complete trees, incomplete trees, and even skewed trees - because we're simply connecting whatever nodes exist at each level in their left-to-right order.
Solution Approach
The solution implements a level-order traversal using BFS (Breadth-First Search) with a queue data structure. Here's how the algorithm works:
1. Initialize the Queue
- Start by checking if the root is
None
. If it is, return immediately. - Create a queue using
deque([root])
and add the root node to begin the traversal.
2. Process Each Level
- The outer
while q:
loop continues as long as there are nodes to process. - At the start of each level, we capture the current queue size with
len(q)
. This tells us exactly how many nodes are at the current level. - Initialize a pointer
p = None
to track the previous node at this level.
3. Connect Nodes Within Each Level
- The inner
for _ in range(len(q)):
loop processes exactly the number of nodes at the current level. - For each node:
- Dequeue it with
node = q.popleft()
- If
p
exists (not the first node in this level), setp.next = node
to create the connection - Update
p = node
to make the current node the "previous" for the next iteration - Add the node's children to the queue for the next level: first
node.left
, thennode.right
(if they exist)
- Dequeue it with
4. Level Completion
- After processing all nodes at a level, the last node's
next
automatically remainsNULL
(its initial value). - The algorithm moves to the next level with a fresh
p = None
.
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. In the worst case (a complete binary tree), the last level could contain up to n/2
nodes, making it O(n)
.
The beauty of this approach is that the queue naturally maintains the left-to-right order of nodes at each level, and by processing exactly len(q)
nodes at a time, we ensure we only connect nodes within the same level.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example to illustrate the solution approach. Consider this binary tree:
1 / \ 2 3 / \ 4 5
Initial State:
- All
next
pointers areNULL
- Queue:
[1]
Level 1 Processing (1 node):
len(q) = 1
, so we'll process 1 nodep = None
(start of level)- Dequeue node 1:
- Since
p = None
, we don't set anynext
pointer - Update
p = node 1
- Add children: Queue becomes
[2, 3]
- Since
- Result: Node 1's
next
remainsNULL
Level 2 Processing (2 nodes):
len(q) = 2
, so we'll process 2 nodesp = None
(start of new level)- Iteration 1 - Dequeue node 2:
- Since
p = None
, we don't set anynext
pointer - Update
p = node 2
- Add children: Queue becomes
[3, 4]
- Since
- Iteration 2 - Dequeue node 3:
- Since
p = node 2
, setnode 2.next = node 3
✓ - Update
p = node 3
- Add children: Queue becomes
[4, 5]
- Since
- Result: Node 2 → Node 3, Node 3's
next
remainsNULL
Level 3 Processing (2 nodes):
len(q) = 2
, so we'll process 2 nodesp = None
(start of new level)- Iteration 1 - Dequeue node 4:
- Since
p = None
, we don't set anynext
pointer - Update
p = node 4
- No children to add
- Since
- Iteration 2 - Dequeue node 5:
- Since
p = node 4
, setnode 4.next = node 5
✓ - Update
p = node 5
- No children to add
- Since
- Result: Node 4 → Node 5, Node 5's
next
remainsNULL
Final connections:
- Level 1: 1 →
NULL
- Level 2: 2 → 3 →
NULL
- Level 3: 4 → 5 →
NULL
The key insight is that by processing exactly len(q)
nodes at each level and maintaining the previous node pointer p
, we naturally create the left-to-right connections within each level while keeping levels separate.
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 of the binary tree
23
24 Returns:
25 The root of the modified tree with next pointers set
26 """
27 # Handle empty tree case
28 if root is None:
29 return root
30
31 # Initialize queue with root node for BFS
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 number of nodes in the current level
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
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 * Uses BFS (level-order traversal) to process nodes level by level.
28 *
29 * @param root The root of the binary tree
30 * @return The root of the modified tree with next pointers set
31 */
32 public Node connect(Node root) {
33 // Handle empty tree case
34 if (root == null) {
35 return root;
36 }
37
38 // Initialize queue for BFS traversal
39 Deque<Node> queue = new ArrayDeque<>();
40 queue.offer(root);
41
42 // Process tree level by level
43 while (!queue.isEmpty()) {
44 // Track the previous node in the current level
45 Node previousNode = null;
46
47 // Get the number of nodes in the current level
48 int levelSize = queue.size();
49
50 // Process all nodes in the current level
51 for (int i = 0; i < levelSize; i++) {
52 // Dequeue the current node
53 Node currentNode = queue.poll();
54
55 // Connect previous node to current node if previous exists
56 if (previousNode != null) {
57 previousNode.next = currentNode;
58 }
59
60 // Update previous node for next iteration
61 previousNode = currentNode;
62
63 // Add left child to queue if it exists
64 if (currentNode.left != null) {
65 queue.offer(currentNode.left);
66 }
67
68 // Add right child to queue if it exists
69 if (currentNode.right != null) {
70 queue.offer(currentNode.right);
71 }
72 }
73 }
74
75 return root;
76 }
77}
78
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 the left child to the queue if it exists
54 if (currentNode->left) {
55 nodeQueue.push(currentNode->left);
56 }
57
58 // Add the right child to the queue if it exists
59 if (currentNode->right) {
60 nodeQueue.push(currentNode->right);
61 }
62 }
63 // 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 the same level of a binary tree.
19 * Uses level-order traversal (BFS) to process nodes level by level.
20 *
21 * @param root - The root node of the binary tree
22 * @returns The root of the modified tree with next pointers set
23 */
24function connect(root: Node | null): Node | null {
25 // Handle empty tree case
26 if (!root) {
27 return null;
28 }
29
30 // Initialize queue with root node for BFS traversal
31 const currentLevelQueue: Node[] = [root];
32
33 // Process tree level by level
34 while (currentLevelQueue.length > 0) {
35 // Queue to store nodes of the next level
36 const nextLevelQueue: Node[] = [];
37
38 // Previous node in the current level for linking
39 let previousNode: Node | null = null;
40
41 // Process all nodes in the current level
42 for (const currentNode of currentLevelQueue) {
43 // Link previous node to current node if exists
44 if (previousNode) {
45 previousNode.next = currentNode;
46 }
47
48 // Update previous node reference
49 previousNode = currentNode;
50
51 // Add children to next level queue if they exist
52 const { left, right } = currentNode;
53 if (left) {
54 nextLevelQueue.push(left);
55 }
56 if (right) {
57 nextLevelQueue.push(right);
58 }
59 }
60
61 // Replace current level queue with next level queue
62 currentLevelQueue.splice(0, currentLevelQueue.length, ...nextLevelQueue);
63 }
64
65 return root;
66}
67
Time and Space Complexity
Time Complexity: O(n)
The algorithm uses BFS (Breadth-First Search) to traverse the binary tree level by level. Each node in the tree is visited exactly once - it's added to the queue once and processed once when it's dequeued. For each node, we perform constant time operations: dequeuing, setting the next
pointer, and enqueuing its children. Since we visit all n
nodes exactly once and perform O(1)
operations for each node, the total time complexity is O(n)
.
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 maximum width occurs at the last level, which can contain up to n/2
nodes (approximately half of all nodes in a complete binary tree are at the last level). Therefore, the space complexity is O(n/2)
= O(n)
. Additionally, we use a constant amount of extra space for variables like p
and node
, but this doesn't affect the overall space complexity.
Common Pitfalls
1. Not Maintaining Level Boundaries
One of the most common mistakes is failing to properly track where one level ends and the next begins. Without capturing level_size = len(queue)
at the start of each level, you might accidentally connect nodes from different levels.
Incorrect approach:
# WRONG: This connects nodes across different levels
while queue:
node = queue.popleft()
if queue: # This might be a node from the next level!
node.next = queue[0]
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
Solution: Always capture the queue size before processing each level and iterate exactly that many times.
2. Forgetting to Reset the Previous Pointer
Another pitfall is not resetting the previous_node
pointer to None
at the start of each new level. This could cause the last node of one level to incorrectly point to the first node of the next level.
Incorrect approach:
# WRONG: previous_node not reset between levels
previous_node = None
while queue:
level_size = len(queue)
# Missing: previous_node = None here!
for _ in range(level_size):
current_node = queue.popleft()
if previous_node:
previous_node.next = current_node
previous_node = current_node
# ... add children
Solution: Initialize previous_node = None
inside the outer while loop, right after determining the level size.
3. Using the Wrong Queue Operations
Using pop()
instead of popleft()
would process nodes in LIFO order rather than FIFO, breaking the left-to-right traversal requirement.
Incorrect approach:
# WRONG: Using pop() gives LIFO behavior current_node = queue.pop() # This gets the last element!
Solution: Always use popleft()
for BFS to maintain FIFO order and ensure left-to-right processing.
4. Modifying Queue Size During Level Processing
Some might try to optimize by checking while queue and level_size > 0:
but if you're not careful with the counter, you might process nodes from multiple levels together.
Solution: Use a simple for _ in range(level_size):
loop which guarantees exactly level_size
iterations, making the logic cleaner and less error-prone.
Which algorithm is best for finding the shortest distance between two points in an unweighted graph?
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!