Topics › Binary Trees › Medium Problems
Populating Next Right Pointers in Each Node
LeetCode ↗ Java source ↗ Edit this article ↗
Jump to code:
Problem
You are given a perfect binary tree: every internal node has exactly two children and all leaves are on the same level. Besides left and right, each node has a pointer next, initially null. Set every node’s next to the node immediately to its right on the same level. The rightmost node of each level keeps next == null. Return the root.
Although the SDE sheet lists this problem on its BST day, the tree is not a search tree and values play no role; the problem is about levels.
Constraints (LeetCode 116): 0 to 2^12 - 1 nodes, values in [-1000, 1000]. The follow-up asks for constant extra space. The Java file tests every perfect tree up to depth 17 (131071 nodes).
Example 1
before: after:
1 1 -> null
/ \ / \
2 3 2 -> 3 -> null
/ \ / \ / \ / \
4 5 6 7 4->5->6->7 -> null
serialized answer: [1, #, 2, 3, #, 4, 5, 6, 7, #]
The LeetCode serialization reads each level by following next pointers and ends each level with #.
Example 2
root = [] // empty tree
answer = []
There are two different kinds of links to create. Sibling links join two children of the same parent (2 -> 3, 4 -> 5). Cousin links cross a boundary between parents (5 -> 6), and they are the interesting part.
Approach 1: Level order traversal with a queue
Intuition. “Next node on the same level” is precisely the order in which a breadth-first traversal produces a level. Process the tree one level at a time (read the queue size at the start of each round), and link each dequeued node to the one dequeued just before it. The first node of a level has no predecessor, and the last node is never assigned a next, so it stays null.
Algorithm.
- If the root is
null, returnnull. Enqueue the root. - While the queue is not empty:
- Let
sizebe the current queue size andprev = null. - Dequeue
sizenodes. For each, setprev.next = nodeifprevexists, thenprev = node, and enqueue its children.
- Let
- Return the root.
static Node levelOrder(Node root) {
if (root == null) return null;
Deque<Node> queue = new ArrayDeque<>();
queue.add(root);
while (!queue.isEmpty()) {
int size = queue.size();
Node prev = null;
for (int i = 0; i < size; i++) {
Node node = queue.poll();
if (prev != null) prev.next = node; // the last node of the level keeps next == null
prev = node;
if (node.left != null) queue.add(node.left);
if (node.right != null) queue.add(node.right);
}
}
return root;
}
Complexity. Time O(n), each node enqueued and dequeued once. Space O(n): the last level of a perfect tree holds (n + 1) / 2 nodes, all in the queue at once. This works for any binary tree, not only perfect ones, but it does not meet the follow-up’s constant-space requirement.
Approach 2: Recursion that wires each node’s children
Intuition. Let each node take responsibility for the next pointers of its two children.
- The sibling link is local:
node.left.next = node.right. - The cousin link from
node.rightmust go to the left child of the node to the right ofnode, which isnode.next.left. So ifnode.nextis already set, the cousin link is one assignment.
Is node.next always ready when we need it? A node’s next is set by its parent, and the parent sets both children’s next pointers before recursing into either child. So by the time connect(child) runs, child.next is final. The root’s next is null, which is already correct. By induction on depth, every call sees a correct next pointer for its own node.
The perfect-tree guarantee is what makes this safe: if a node has a left child it also has a right child, and if node.next exists it also has a left child.
Algorithm.
- If the node is
nullor a leaf (no left child), return. node.left.next = node.right.- If
node.next != null:node.right.next = node.next.left. - Recurse into the left child, then the right child.
static Node recursive(Node root) {
connect(root);
return root;
}
static void connect(Node node) {
if (node == null || node.left == null) return; // perfect tree: no left child means a leaf
node.left.next = node.right; // siblings
if (node.next != null) node.right.next = node.next.left; // cousins across the parent boundary
connect(node.left);
connect(node.right);
}
Complexity. Time O(n), constant work per node. Space O(log n) for the recursion, since a perfect tree with n nodes has height log2(n + 1). That is already tiny, but it is not strictly constant.
Approach 3: Optimal, walk each finished level as a linked list
Intuition. Approach 2 never really needed recursion. Once a level is fully linked, it is a linked list: start at its leftmost node and follow next. While walking that list we can do exactly the same two assignments for each node’s children, which links the level below. Then move down to the leftmost node of the next level (leftmost.left) and repeat. The next pointers we just created serve as the “queue” for the following level, so no extra memory is needed.
Algorithm.
leftmost = root.- While
leftmostexists and has children:- For
curfromleftmostalongnext: setcur.left.next = cur.right, and ifcur.next != null, setcur.right.next = cur.next.left. leftmost = leftmost.left.
- For
- Return the root.
static Node optimal(Node root) {
Node leftmost = root;
while (leftmost != null && leftmost.left != null) {
for (Node cur = leftmost; cur != null; cur = cur.next) {
cur.left.next = cur.right;
if (cur.next != null) cur.right.next = cur.next.left;
}
leftmost = leftmost.left; // first node of the next level
}
return root;
}
Dry run on Example 1:
leftmost |
cur |
links created |
|---|---|---|
| 1 | 1 | 2 → 3 (sibling); 1 has no next |
| 2 | 2 | 4 → 5 (sibling), 5 → 6 (cousin, via 2.next = 3) |
| 2 | 3 | 6 → 7 (sibling); 3 has no next |
| 4 | 4 is a leaf, stop |
Complexity. Time O(n): every internal node is cur exactly once (leaves never are), and each step is constant work. Space O(1): only leftmost and cur.
Edge cases and pitfalls
- Empty tree. Approach 1 returns at once, Approach 2 returns at the base case, Approach 3 never enters the loop. All return
null. - Single node. It is both the leftmost and the rightmost node of its level, so
nextstaysnull. - Linking across levels. In the BFS, capture
sizebefore the inner loop and resetprevper level. Otherwise the last node of one level gets linked to the first node of the next. - Trees that are not perfect. Approaches 2 and 3 assume every internal node has two children. For an arbitrary tree (LeetCode 117), the cousin of a node may be several parents away. The BFS still works, and the constant-space version is adapted by building each next level with a dummy head node and a tail pointer.
- Testing. The tests rebuild a fresh tree for each approach, check that no
nextpointers exist beforehand, and compare the LeetCode-style serialization obtained by followingnextpointers level by level. Because the serialization stops atnull, a missing or wrong link changes it.
Related problems
- Level Order Traversal is the queue pattern behind Approach 1.
- Right/Left View of BT: after linking, the right view is the set of nodes whose
nextisnull. - Maximum Width of BT also reasons about positions within one level.
- Zig Zag or Spiral Traversal processes the tree level by level with alternating direction.
- Flatten Binary Tree to Linked List is another problem that rewires pointers in place with
O(1)extra space.