Skip to content

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 O(1)O(1)-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:

copy_random_list.py
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]
copy_random_list.py
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]

O(n)O(n) time, O(n)O(n) space for the map.

Pattern 2 — composition

LC 143 asks you to reorder 1234512345 into 1524315243. That is not a single technique; it is three, in a fixed order:

reorder_list.py
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 head
reorder_list.py
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 head

The 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.

flatten_multilevel.py
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 head
flatten_multilevel.py
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 head

Three 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.

ProblemTimeSpace
138 with a mapO(n)O(n)O(n)O(n)
138 interleavedO(n)O(n)O(1)O(1)
143 reorderO(n)O(n)O(1)O(1)
430 flattenO(n)O(n)O(1)O(1) iterative, O(depth)O(\text{depth}) recursive

The variant map

VariantThe techniqueCanonical problem
Deep copy with arbitrary pointersMap then wire, or interleave138
Deep copy a graphThe same map, with DFS/BFS133
Interleave halvesMiddle + reverse + merge143
Flatten a nested listSplice each child list inline430
BST to doubly linked listIn-order traversal, relinking as you go426 (Premium)
Split by odd/even positionTwo dummy heads328
Palindrome checkMiddle + reverse + compare234

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 O(n)O(n) — two passes. Space O(n)O(n) 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 O(1)O(1) extra space.” The interleaving trick described above: weave copies in after their originals so that original.random.nextoriginal.random.next is the copy of original.randomoriginal.random, then unweave. This is the standard follow-up, and the key line is copy.random = original.random.nextcopy.random = original.random.next.
  • “One pass instead of two?” Yes, with a defaultdictdefaultdict that creates a copy on first access — so a forward randomrandom reference creates the node early and the later nextnext pass 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 O(n)O(n), 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 O(n)O(n) — three linear passes. Space O(1)O(1).

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 lands slowslow on the last node of the first half for both parities. For [1,2,3,4][1,2,3,4] it stops with slowslow at 22, splitting [1,2][1,2] and [3,4][3,4]. Using while fast and fast.nextwhile fast and fast.next puts slowslow one step further and breaks the even case.
  • Name the pointers before rewiring. first_nextfirst_next and second_nextsecond_next must be captured before either nextnext is 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 O(n)O(n)-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?” — O(n)O(n) space; fine, but the point is the O(1)O(1) 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 O(n)O(n). 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 O(1)O(1).

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.
  • prevprev pointers — both child.prev = nodechild.prev = node and after.prev = tailafter.prev = tail. The second needs an if afterif after guard, 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 O(depth)O(\text{depth}) 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

#ProblemDifficultyThe twist
138Copy List with Random PointerMediumCreate all copies, then wire; or interleave for O(1)O(1) space
143Reorder ListMediumMiddle + reverse + interleave; the split is mandatory
430Flatten a Multilevel Doubly Linked ListMediumSplice inline; maintain prevprev and clear childchild
148Sort ListMediumMerge sort — see Divide and Conquer
328Odd Even Linked ListMediumSplit by position parity with two dummy heads
133Clone GraphMediumThe same map-then-wire idea, with DFS/BFS instead of a linear walk
426Convert Binary Search Tree to Sorted Doubly Linked ListMedium · PremiumIn-order traversal, relinking as you visit

Interview follow-ups

They askWhat they’re checkingThe answer
“Why two passes for the copy?”The ordering problemA randomrandom pointer may target a node not yet copied, so nothing can be wired until all nodes exist
“Do it in O(1)O(1) space”The known follow-upInterleave 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 awarenessWithout slow.next = Noneslow.next = None the reversed half creates a cycle and the loop never ends
“Which fast/slow condition?”Precisionwhile 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 specLC 430 requires it, and a forward walk looks correct without it
“Recursion or iteration for 430?”JudgementIterative splicing is O(1)O(1) space and handles nesting for free; recursion is O(depth)O(\text{depth})
“Do you mutate the input?”Design sense143 and 430 do, by specification; say so, and offer to restore if it matters

Edge-case checklist

  • Empty listNoneNone in, NoneNone out 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.
  • randomrandom pointing to NoneNone — must map to NoneNone, not raise.
  • randomrandom pointing to itself or backwards — both legal; the map handles them.
  • All randomrandom pointers NoneNone — reduces to a plain copy.
  • Child at the last node (LC 430) — afterafter is NoneNone, so guard after.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 dictdict from original to copy. Use .get().get() so NoneNone maps to NoneNone.
  • The O(1)O(1)-space deep copy interleaves copies into the original list, making the list itself the map: orig.random.nextorig.random.next is the copy of orig.randomorig.random.
  • Reorder List is a composition: find the middle, split, reverse the second half, interleave. The slow.next = Noneslow.next = None split 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 themnextnext, prevprev, and clearing childchild.
  • 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 coffee

Was this page helpful?

Let us know how we did