Copy Flatten and Reorder
The earlier linked-list pages are pure pointer work: fast and slow for structure, reversal for direction, dummy heads for building. This page covers the problems that need something more:
- A hash map, when a node’s copy must be found before it has been created.
- A composed traversal, when the answer is three known techniques applied in sequence.
- Careful field maintenance, when nodes carry more than
nextnext.
These are the linked-list problems most likely to appear in a senior loop, because they test whether you can keep several invariants straight at once rather than execute one memorised template.
What you’ll learn
- The map-then-wire two-pass deep copy, and the -space interleaving alternative.
- Why reorder list is three patterns composed, and the order they must run in.
- Multilevel flattening, and the two fields you must not forget.
- The habit that prevents most bugs here: name what each pointer means before touching it.
- Three real LeetCode problems solved in the browser: 138, 143, 430.
The cue
Pattern 1 — map then wire
For LC 138 each node has a randomrandom pointer that may target any node,
including one you have not copied yet. So you cannot wire pointers while creating
nodes — the target may not exist.
Two passes solve it:
def copy_random_list(head):
if not head:
return None
clone = {} # original node -> its copy
node = head # PASS 1: create every copy
while node:
clone[node] = Node(node.val)
node = node.next
node = head # PASS 2: wire, now that all exist
while node:
clone[node].next = clone.get(node.next) # .get -> None for None
clone[node].random = clone.get(node.random)
node = node.next
return clone[head]def copy_random_list(head):
if not head:
return None
clone = {} # original node -> its copy
node = head # PASS 1: create every copy
while node:
clone[node] = Node(node.val)
node = node.next
node = head # PASS 2: wire, now that all exist
while node:
clone[node].next = clone.get(node.next) # .get -> None for None
clone[node].random = clone.get(node.random)
node = node.next
return clone[head]time, space for the map.
Pattern 2 — composition
LC 143 asks you to reorder 1→2→3→4→51→2→3→4→5 into 1→5→2→4→31→5→2→4→3. That is not a single
technique; it is three, in a fixed order:
def reorder_list(head):
if not head or not head.next:
return head
# 1. find the middle (fast/slow) and SPLIT
slow, fast = head, head
while fast.next and fast.next.next:
slow, fast = slow.next, fast.next.next
second = slow.next
slow.next = None # ESSENTIAL: terminate the first half
# 2. reverse the second half
prev = None
while second:
nxt = second.next
second.next = prev
prev = second
second = nxt
# 3. interleave the two halves
first, second = head, prev
while second:
first_next, second_next = first.next, second.next # name them first
first.next = second
second.next = first_next
first, second = first_next, second_next
return headdef reorder_list(head):
if not head or not head.next:
return head
# 1. find the middle (fast/slow) and SPLIT
slow, fast = head, head
while fast.next and fast.next.next:
slow, fast = slow.next, fast.next.next
second = slow.next
slow.next = None # ESSENTIAL: terminate the first half
# 2. reverse the second half
prev = None
while second:
nxt = second.next
second.next = prev
prev = second
second = nxt
# 3. interleave the two halves
first, second = head, prev
while second:
first_next, second_next = first.next, second.next # name them first
first.next = second
second.next = first_next
first, second = first_next, second_next
return headThe loop condition while fast.next and fast.next.nextwhile fast.next and fast.next.next makes slowslow land on the
end of the first half for both odd and even lengths — which is what you want,
since the first half may be one longer. Using while fast and fast.nextwhile fast and fast.next instead
lands slowslow one further along and breaks the even case.
Pattern 3 — maintain every field
LC 430 flattens a doubly linked list where nodes may have a childchild list. The
algorithm is simple; the bookkeeping is where it goes wrong.
def flatten(head):
if not head:
return None
node = head
while node:
if node.child:
nxt = node.child # splice the child list in here
tail = nxt
while tail.next: # walk to the child list's end
tail = tail.next
after = node.next # remember what came after
node.child = None # MUST be cleared
node.next = nxt
nxt.prev = node
tail.next = after
if after:
after.prev = tail # only if there IS a node after
node = node.next
return headdef flatten(head):
if not head:
return None
node = head
while node:
if node.child:
nxt = node.child # splice the child list in here
tail = nxt
while tail.next: # walk to the child list's end
tail = tail.next
after = node.next # remember what came after
node.child = None # MUST be cleared
node.next = nxt
nxt.prev = node
tail.next = after
if after:
after.prev = tail # only if there IS a node after
node = node.next
return headThree things must all be right: nextnext, prevprev, and child = Nonechild = None. The
problem explicitly requires all childchild pointers to be null in the result, and it
is the one most easily forgotten because the list looks correct when you walk it
forwards.
| Problem | Time | Space |
|---|---|---|
| 138 with a map | ||
| 138 interleaved | ||
| 143 reorder | ||
| 430 flatten | iterative, recursive |
The variant map
| Variant | The technique | Canonical problem |
|---|---|---|
| Deep copy with arbitrary pointers | Map then wire, or interleave | 138 |
| Deep copy a graph | The same map, with DFS/BFS | 133 |
| Interleave halves | Middle + reverse + merge | 143 |
| Flatten a nested list | Splice each child list inline | 430 |
| BST to doubly linked list | In-order traversal, relinking as you go | 426 (Premium) |
| Split by odd/even position | Two dummy heads | 328 |
| Palindrome check | Middle + reverse + compare | 234 |
Practice — real LeetCode problems
LC 138 — Copy List with Random Pointer · Medium
Problem. Each node has a valval, a nextnext and a randomrandom pointer that may
point to any node in the list or to NoneNone. Return a deep copy: entirely new
nodes, with nextnext and randomrandom pointing to the corresponding copies.
Constraints. 0 <= n <= 10000 <= n <= 1000, -10^4 <= Node.val <= 10^4-10^4 <= Node.val <= 10^4.
Examples. For [[7,null],[13,0],[11,4],[10,2],[1,0]][[7,null],[13,0],[11,4],[10,2],[1,0]] the copy must have the
same values and the same randomrandom structure — and share no nodes with the
original.
Editorial — approach, complexity, follow-ups
The randomrandom pointer is the whole difficulty: it can point forwards, so at the
moment you copy a node its randomrandom target may not exist yet. Separating creation
from wiring removes the ordering problem entirely.
Time — two passes. Space for the map.
clone.get(...)clone.get(...) handles NoneNone targets. Indexing with clone[None]clone[None] raises,
and adding an if node.random else Noneif node.random else None conditional on each line is noisier for
the same effect.
Follow-ups you should expect:
- “Do it in extra space.” The interleaving trick described above:
weave copies in after their originals so that
original.random.nextoriginal.random.nextis the copy oforiginal.randomoriginal.random, then unweave. This is the standard follow-up, and the key line iscopy.random = original.random.nextcopy.random = original.random.next. - “One pass instead of two?” Yes, with a
defaultdictdefaultdictthat creates a copy on first access — so a forwardrandomrandomreference creates the node early and the laternextnextpass finds it already present. Neat, and worth knowing. - “Deep copy a graph (LC 133)?” The same map, but traversal is DFS or BFS since there is no linear order.
- “Why not
copy.deepcopycopy.deepcopy?” It works and is , but it defeats the question; mention it and move on.
LC 143 — Reorder List · Medium
Problem. Given L0 → L1 → … → Ln-1 → LnL0 → L1 → … → Ln-1 → Ln, reorder it to
L0 → Ln → L1 → Ln-1 → L2 → …L0 → Ln → L1 → Ln-1 → L2 → …. You may not modify the values, only rearrange
nodes.
Constraints. 1 <= n <= 5 * 10^41 <= n <= 5 * 10^4, 1 <= Node.val <= 10001 <= Node.val <= 1000.
Examples. [1,2,3,4][1,2,3,4] gives [1,4,2,3][1,4,2,3] · [1,2,3,4,5][1,2,3,4,5] gives [1,5,2,4,3][1,5,2,4,3]
Editorial — approach, complexity, follow-uups
The target order takes one node from the front, then one from the back, alternating. Reversing the second half turns “from the back” into “from the front”, after which it is a straightforward interleave.
Time — three linear passes. Space .
Three details, each with a reason:
slow.next = Noneslow.next = None. Without it the first half still links into the second, and after reversal you have a cycle — the interleave loop spins forever and the submission times out rather than failing cleanly.while fast.next and fast.next.nextwhile fast.next and fast.next.next. This landsslowslowon the last node of the first half for both parities. For[1,2,3,4][1,2,3,4]it stops withslowslowat22, splitting[1,2][1,2]and[3,4][3,4]. Usingwhile fast and fast.nextwhile fast and fast.nextputsslowslowone step further and breaks the even case.- Name the pointers before rewiring.
first_nextfirst_nextandsecond_nextsecond_nextmust be captured before eithernextnextis overwritten, or you lose the rest of a list.
The odd case is worth tracing: [1,2,3,4,5][1,2,3,4,5] splits into [1,2,3][1,2,3] and [4,5][4,5], so
the first half is longer. Reversing gives [5,4][5,4], and interleaving ends when
secondsecond runs out, leaving 33 correctly at the tail.
The -space alternative — dump nodes into a list and re-link by index from both ends — is much easier to write and worth offering first if the pointer surgery is not flowing.
Follow-ups you should expect: “Palindrome check (LC 234)?” — the same middle plus reverse, then compare instead of interleave. “Restore the list afterwards?” — re-reverse the second half; mutating a caller’s input is a real design concern. “Odd Even Linked List (LC 328)?” — a different split (by position parity) with two dummy heads. “Why not use a deque?” — space; fine, but the point is the solution.
LC 430 — Flatten a Multilevel Doubly Linked List · Medium
Problem. A doubly linked list where nodes may additionally have a childchild
pointer to another such list. Flatten it into a single-level doubly linked list,
with child lists appearing immediately after their parent node. All childchild
pointers in the result must be NoneNone.
Constraints. 0 <= number of nodes <= 10000 <= number of nodes <= 1000.
Examples. 1-2-3-4-5-61-2-3-4-5-6 with 3.child = 7-8-9-103.child = 7-8-9-10 and 8.child = 11-128.child = 11-12
flattens to 1-2-3-7-8-11-12-9-10-4-5-61-2-3-7-8-11-12-9-10-4-5-6
Editorial — approach, complexity, follow-ups
Walk the list once. Whenever a node has a child, splice that child list in
immediately after the node and clear the childchild pointer. Then keep walking from
node.nextnode.next — which is now the head of the spliced-in list, so any nested children
inside it are handled by the same loop on a later iteration.
Time . Each node is visited once by the outer walk; the inner
while tail.nextwhile tail.next walk visits each node at most once more in total across the
whole run, because a child list is spliced exactly once. Space .
Three requirements must all hold, and the test checks each:
- Order — the child list comes immediately after its parent, before the parent’s original successor.
child = Nonechild = None— explicitly required by the problem, and invisible if you only check the forward walk.prevprevpointers — bothchild.prev = nodechild.prev = nodeandafter.prev = tailafter.prev = tail. The second needs anif afterif afterguard, since the parent may have been the last node.
The elegance is that no recursion or stack is needed. Because the walk
continues into the spliced list, arbitrarily deep nesting resolves naturally —
node 8’s child 11-1211-12 is discovered on a later iteration of the same loop, after
7-8-9-107-8-9-10 has been spliced in.
A recursive or explicit-stack solution also works (push node.nextnode.next, descend into
node.childnode.child), and is space. Worth mentioning; the iterative
splice is neater.
Follow-ups you should expect: “Recursively?” — flatten the child, return its
tail, splice; be careful returning tails correctly. “What if the child list also
had children?” — already handled, and worth pointing out. “Flatten a nested
iterator instead (LC 341)?” — lazy flattening; see
Design Iterators.
“Convert a BST to a sorted doubly linked list (LC 426)?” — in-order traversal
relinking prevprev/nextnext as you go.
LeetCode problem set
| # | Problem | Difficulty | The twist |
|---|---|---|---|
| 138 | Copy List with Random Pointer | Medium | Create all copies, then wire; or interleave for space |
| 143 | Reorder List | Medium | Middle + reverse + interleave; the split is mandatory |
| 430 | Flatten a Multilevel Doubly Linked List | Medium | Splice inline; maintain prevprev and clear childchild |
| 148 | Sort List | Medium | Merge sort — see Divide and Conquer |
| 328 | Odd Even Linked List | Medium | Split by position parity with two dummy heads |
| 133 | Clone Graph | Medium | The same map-then-wire idea, with DFS/BFS instead of a linear walk |
| 426 | Convert Binary Search Tree to Sorted Doubly Linked List | Medium · Premium | In-order traversal, relinking as you visit |
Interview follow-ups
| They ask | What they’re checking | The answer |
|---|---|---|
| “Why two passes for the copy?” | The ordering problem | A randomrandom pointer may target a node not yet copied, so nothing can be wired until all nodes exist |
| “Do it in space” | The known follow-up | Interleave copies after originals so orig.random.nextorig.random.next is the copy of orig.randomorig.random, then unweave |
| “Why does reorder need a split?” | Cycle awareness | Without slow.next = Noneslow.next = None the reversed half creates a cycle and the loop never ends |
| “Which fast/slow condition?” | Precision | while fast.next and fast.next.nextwhile fast.next and fast.next.next lands slowslow at the end of the first half for both parities |
“Did you clear childchild?” | Reading the spec | LC 430 requires it, and a forward walk looks correct without it |
| “Recursion or iteration for 430?” | Judgement | Iterative splicing is space and handles nesting for free; recursion is |
| “Do you mutate the input?” | Design sense | 143 and 430 do, by specification; say so, and offer to restore if it matters |
Edge-case checklist
- Empty list —
NoneNonein,NoneNoneout for all three. - Single node — 143 returns it unchanged; 138 must still deep-copy it.
- Two nodes — 143’s smallest interleave; also where the wrong fast/slow condition first shows up.
randomrandompointing toNoneNone— must map toNoneNone, not raise.randomrandompointing to itself or backwards — both legal; the map handles them.- All
randomrandompointersNoneNone— reduces to a plain copy. - Child at the last node (LC 430) —
afterafterisNoneNone, so guardafter.prevafter.prev. - Nested children — 8’s child inside 3’s child; the inline walk covers it.
- Odd vs even length (LC 143) —
[1,2,3][1,2,3]gives[1,3,2][1,3,2]; the first half may be longer. - Forgetting to terminate a split — produces a cycle and a timeout, not a wrong answer.
Recap
- When a pointer can target a node that does not exist yet, create everything
first and wire second — a
dictdictfrom original to copy. Use.get().get()soNoneNonemaps toNoneNone. - The -space deep copy interleaves copies into the original list, making
the list itself the map:
orig.random.nextorig.random.nextis the copy oforig.randomorig.random. - Reorder List is a composition: find the middle, split, reverse the second
half, interleave. The
slow.next = Noneslow.next = Nonesplit is what prevents a cycle. - Name pointers in locals before rewiring. Every bug in this family is a lost reference.
- When nodes carry extra fields, maintain all of them —
nextnext,prevprev, and clearingchildchild. - Splicing a nested list inline and continuing the walk handles arbitrary nesting with no recursion.
- LC 138 and LC 133 are the same problem; the map doubles as the visited set on a graph.
Next: the tree patterns — recursion over branching structures rather than linear ones.
If this helped you, consider buying me a coffee ☕
Buy me a coffeeWas this page helpful?
Let us know how we did
