Flatten a Multilevel Doubly Linked List: DFS Pointer Manipulation
You are given a doubly linked list where each node has three pointers: next, prev, and child. The child pointer may point to a separate doubly linked list, which itself can have nodes with child pointers, creating multiple levels of nesting. Your task is to flatten this structure into a single-level doubly linked list. When a node has a child list, that child list should be inserted between the node and its original next neighbor.
This is LeetCode 430: Flatten a Multilevel Doubly Linked List, and it is a great test of whether you can do precise pointer manipulation without losing track of nodes.
Why this problem matters
Flattening a multilevel linked list combines two skills that show up constantly in interviews: depth-first traversal and pointer rewiring. You need to handle the child lists in a DFS-like order (go deeper before continuing forward), and you need to stitch pointers without orphaning any part of the list.
This same pattern appears in tree flattening problems like Flatten Binary Tree to Linked List, where you transform a hierarchical structure into a linear one. The difference here is that you are working with doubly linked pointers, so you need to maintain both next and prev connections. If you forget to update prev, you end up with a list that works going forward but breaks going backward.
The problem also teaches you a reusable trick: when you need to splice a sublist into a linked list, save the next pointer, connect the sublist, walk to the sublist's tail, then reconnect to the saved node. You will use this technique in many linked list problems.
The approach
The iterative approach walks through the list node by node. When you encounter a node with a child pointer, you do three things:
- Find the tail of the child list by walking to its end.
- Connect the child list's tail to the current node's next neighbor.
- Connect the current node to the child list's head, and clear the child pointer.
Then you continue walking forward. Because the child list is now part of the main list, any nested children within it will be handled naturally as you keep iterating.
def flatten(head):
if not head:
return head
curr = head
while curr:
if curr.child:
child_head = curr.child
child_tail = child_head
while child_tail.next:
child_tail = child_tail.next
child_tail.next = curr.next
if curr.next:
curr.next.prev = child_tail
curr.next = child_head
child_head.prev = curr
curr.child = None
curr = curr.next
return head
The key detail is that we find the child_tail before doing any rewiring. This guarantees we have a handle on the end of the child list so we can connect it to whatever comes after the current node. If curr.next is None (the current node is the last node), we just leave child_tail.next as None, which is already correct.
Notice that we also set curr.child = None after splicing. The problem requires all child pointers to be null in the final result.
Step-by-step walkthrough
Let's trace the algorithm on a list where Level 1 is 1 <-> 2 <-> 3 <-> 4 and node 2 has a child list 5 <-> 6.
Step 1: Start at node 1. No child pointer. Move to next.
Node 1 has no child. Simply advance curr to the next node (2).
Step 2: At node 2, found a child. Save next=3, connect 2 to 5.
Found child at node 2. Save curr.next (3). Set curr.next = child_head (5), set 5.prev = curr (2). Clear child pointer.
Step 3: Find tail of child list (6). Connect 6 to saved next (3).
Walk the child list to find its tail (6). Set child_tail.next = saved next (3), set 3.prev = child_tail (6). The list is now connected.
Step 4: Continue from 3. No child at 3 or 4. Done.
Nodes 3 and 4 have no children. We walk to the end without any more rewiring. The list is fully flattened: 1, 2, 5, 6, 3, 4.
The algorithm handles deeper nesting naturally. If node 5 also had a child list, it would get spliced in when curr reaches node 5 during the iteration. Because the child list is inserted right after the current node, any children within it are guaranteed to be processed before we reach the original next node.
Complexity analysis
| Metric | Value |
|---|---|
| Time | O(n) |
| Space | O(1) |
Time: We visit every node exactly once as curr, plus we walk each child list once to find its tail. Since every node belongs to exactly one child list (or the top level), the total work across all tail-finding walks is O(n). Each node is visited at most twice total.
Space: We use a constant number of pointer variables (curr, child_head, child_tail). No stack, no recursion, no hash map. True O(1) extra space.
Edge cases to watch for
- Empty list (
head is None): returnNoneimmediately. - Single node with no child: already flat. Return it as-is.
- Single node with a child list: the child list becomes the entire rest of the flat list.
- Deeply nested children (child of child of child): the algorithm handles this naturally. Each child list gets spliced in before we reach its parent's next, so deeper levels are flattened first.
- Child list at the last node:
curr.nextisNone, so we skip thecurr.next.prev = child_tailassignment. The child list becomes the new tail of the flat list. - All nodes have children: every node triggers splicing. The algorithm still runs in O(n) time because each node is visited at most twice.
The building blocks
This problem combines two reusable patterns:
Linked list splicing. The core operation is inserting a sublist between two nodes. Save the next pointer, connect the head of the sublist, walk to its tail, then connect the tail to the saved node. This same technique appears in Reverse Linked List (where you splice reversed segments) and any problem that requires inserting or moving chunks of a linked list.
DFS-order processing. By iterating forward through the list and splicing child lists inline, we process nodes in a depth-first order. When we hit a child, we go "deeper" by inserting the child list right here. This is the same traversal order you see in Flatten Binary Tree to Linked List, just expressed iteratively through pointer manipulation instead of recursion.
From understanding to recall
You have seen the algorithm, traced it step by step, and understand why each pointer assignment is necessary. But the details are where interviews get tricky. Do you remember to find the child tail before rewiring? Do you remember to set child.prev = curr? Do you remember to clear curr.child?
These are not conceptual gaps. They are recall gaps. You understand the logic, but under time pressure, one missed prev pointer means your solution breaks on the backward traversal.
Spaced repetition closes that gap. You write the solution from memory at increasing intervals until the pointer sequence becomes automatic. When a linked list flattening problem appears, you do not pause to rederive it. You just write it.
Related posts
- Flatten Binary Tree to Linked List - The tree version of the same flattening pattern, transforming a hierarchical structure into a linear one
- Reverse Linked List - The foundational linked list pointer manipulation problem
- Copy List with Random Pointer - Another linked list problem with extra pointers that require careful handling