Populating Next Right Pointers II: Handling Non-Perfect Trees
Given a binary tree where each node has a left, right, and next pointer, populate each next pointer to point to the node immediately to its right on the same level. If there is no right neighbor, set next to null.
This is LeetCode 117: Populating Next Right Pointers in Each Node II. Unlike LeetCode 116, the tree is not necessarily perfect. Any node might be missing its left child, right child, or both. You need to handle all of these cases while using only O(1) extra space.
Why this is harder than the perfect-tree version
In a perfect binary tree, every node has exactly two children and every level is full. That means the next-level connections follow a predictable pattern: node.left.next = node.right, and node.right.next = node.next.left. You can hardcode these two rules.
With an arbitrary binary tree, that pattern breaks. A node might have only a left child, only a right child, or no children at all. When you need to find the "next right" child on the next level, you might have to skip across multiple parents that have no children on the relevant side. The simple two-rule approach no longer works.
The solution is a dummy-node technique that builds the next level's linked list from scratch as you traverse the current level.
The approach: dummy node linked list construction
The idea is to process the tree level by level, but instead of using a queue (which costs O(n) space), you use the next pointers you have already built on the current level to traverse it. For the next level, you use a dummy node as the head of a temporary linked list:
- Create a
dummynode. It acts as a placeholder head for the next level's linked list. - Use a
tailpointer, starting atdummy, to append children as you find them. - Walk
curracross the current level usingnextpointers. - For each
curr, if it has a left child, settail.next = curr.leftand advancetail. If it has a right child, settail.next = curr.rightand advancetail. - When
currreachesnull(end of the current level), the next level starts atdummy.next. Resetdummy.next = null, resettail = dummy, and setcurr = dummy.nextfrom the previous iteration.
The dummy node solves the "which child comes first on the next level?" problem. You do not need to know in advance whether the first child is a left or right child, or which parent it belongs to. You just append every child you find, in left-to-right order, to the tail of the linked list.
Python solution
class Node:
def __init__(self, val=0, left=None, right=None, next=None):
self.val = val
self.left = left
self.right = right
self.next = next
def connect(root):
curr = root
while curr:
dummy = Node(0)
tail = dummy
while curr:
if curr.left:
tail.next = curr.left
tail = tail.next
if curr.right:
tail.next = curr.right
tail = tail.next
curr = curr.next
curr = dummy.next
return root
Visual walkthrough
Let's trace through the tree [1, 2, 3, 4, 5, null, 7]. Watch how the dummy node builds each level's linked list as curr walks across the current level via next pointers.
Step 1: Process Level 0. curr = node 1. Create dummy for Level 1.
curr: 1
dummy.next: null (will point to first child found)
tail: dummy
Starting at the root. The dummy node will help us find the first node on Level 1.
Step 2: Node 1 has children 2 and 3. Link them via the dummy.
curr: 1 (done with Level 0)
dummy.next: 2 (first child on Level 1)
tail: 3 (last linked child so far)
tail.next = 2, then tail moves to 2. tail.next = 3, then tail moves to 3. The next-level list is: 2 -> 3 -> null.
Step 3: Move to Level 1. curr = 2. Create fresh dummy for Level 2.
curr: 2 (walking Level 1 via next pointers)
dummy.next: 4 (first child on Level 2)
tail: 5
Node 2 has children 4 and 5. Link them: dummy -> 4, then 4 -> 5. tail is now 5.
Step 4: Follow next. curr = 3. It has only right child 7.
curr: 3 (no left child, right child is 7)
tail.next: 7 (linked 5 -> 7)
tail: 7
Node 3 has no left child. Its right child 7 is linked after 5. Level 2 list is now 4 -> 5 -> 7 -> null.
Step 5: Level 1 done. Move to Level 2. curr = 4. No children on any Level 2 node.
curr: walks 4 -> 5 -> 7 via next pointers
dummy.next: null (no children found)
Result: all next pointers are set
Walking Level 2, none of these nodes have children. dummy.next stays null, so the outer loop ends. All next pointers are connected.
The critical moment is Step 4. When curr moves from node 2 to node 3 (via the next pointer built in the previous iteration), the tail pointer has already linked 4 and 5. Node 3 has no left child, so it only contributes its right child (7). The tail links 5 to 7, bridging across two different parents. That is the power of the dummy node: it does not care which parent a child belongs to.
How the code works, line by line
The outer while curr loop runs once per level. At the start of each iteration, curr is the first node on the current level (the root for Level 0, then dummy.next for each subsequent level).
Inside, the inner while curr loop walks across the current level using next pointers. For each node, it checks for left and right children and appends them to the tail of the next-level linked list. The tail.next = curr.left and tail.next = curr.right lines are the same operation: "append this child to the end of the list and advance tail."
When the inner loop finishes, dummy.next points to the first node on the next level. Setting curr = dummy.next drops down one level and starts the process again. If dummy.next is null, there are no more levels, and the outer loop ends.
The dummy node is reset each time the outer loop iterates, because a new dummy = Node(0) is created at the top of the loop. This keeps the solution clean: each level gets its own fresh dummy.
The dummy node eliminates all edge-case branching. Without it, you would need special logic to identify the first child on each level, track whether you have started the linked list yet, and handle the case where curr has no children. The dummy absorbs all of that complexity into a single, uniform "append to tail" operation.
Complexity analysis
Time: O(n). Every node is visited exactly once as curr on its level. Each child link operation is O(1). Total work is proportional to the number of nodes.
Space: O(1). The only extra memory is the dummy node and the tail/curr pointers. No queue, no recursion stack. The next pointers are part of the output, not extra space.
The building blocks
This problem is built on two reusable pieces that CodeBricks drills independently:
1. Dummy-head linked list construction
The pattern of using a dummy node to simplify linked list building:
dummy = ListNode(0)
tail = dummy
while condition:
tail.next = new_node
tail = tail.next
return dummy.next
You see this pattern in Merge Two Sorted Lists, Partition List, and any problem where you are building a linked list without knowing the head in advance. The dummy node removes the "is this the first node?" check and lets you always use tail.next = ... uniformly.
2. Level traversal via next pointers
The pattern of walking across a level using the next pointers you have already set:
curr = level_start
while curr:
process(curr)
curr = curr.next
This replaces a BFS queue with O(1) space traversal. You can only use this pattern when the current level already has its next pointers wired up, which is exactly what the previous iteration accomplished. This is the same traversal used in Populating Next Right Pointers, and it generalizes to any problem where a prior pass has linked nodes into a traversable list.
Edge cases
- Empty tree:
rootisnull. The outerwhile currnever executes, and we returnnull. No special check needed. - Single node: the root has no children. The inner loop runs once, finds no children, so
dummy.nextisnull. The outer loop ends after one iteration. The root'snextstaysnull. - All nodes on one side (skewed tree): every node has only a left child (or only a right child). The dummy node handles this naturally. Each level has exactly one node, and each node's
nextisnull. The dummy just links each single child as the sole member of the next level.
A common mistake is trying to adapt the perfect-tree solution (LeetCode 116) by adding null checks. That approach gets messy fast because you need to search for the "next available child" across potentially many parents. The dummy node approach avoids that entirely by treating every child uniformly.
From understanding to recall
You understand the dummy-node technique. You see how tail builds the next-level list while curr walks the current level. The logic is clean. But under interview pressure, the nesting of two while loops, the dummy reset, and the curr = dummy.next handoff are easy to fumble.
The details that trip people up: creating a fresh dummy inside the outer loop (not before it), using tail to append (not dummy), and remembering that curr = dummy.next is how you drop to the next level. These are mechanical things that spaced repetition makes automatic.
You practice writing the two-loop structure from scratch at increasing intervals. After a few rounds, the dummy-node pattern flows naturally. You do not need to re-derive the approach. You just write it.
Related posts
- Populating Next Right Pointers in Each Node - The perfect-tree version (LeetCode 116), where next-pointer rules are simpler because every level is full
- Binary Tree Level Order Traversal - The BFS queue approach to level-by-level processing, which this problem optimizes to O(1) space
CodeBricks breaks this problem into its dummy-head linked list construction and level-traversal-via-next-pointers building blocks, then drills them independently with spaced repetition. You type each piece from scratch until the two-loop dummy-node pattern is automatic. When you hit any tree-linking problem in your interview, you do not think about it. You just write it.