TL;DR
Find middle + reverse second half + alternate merge β O(n) time, O(1) space.
Approach 1 β Brute force: array of nodes, two indices
Load every node into a Python list; then left and right indices walking inward give the exact output order, and you relink as you go.
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def reorderList(self, head: Optional[ListNode]) -> None:
nodes: List[ListNode] = []
node = head
while node:
nodes.append(node)
node = node.next
left, right = 0, len(nodes) - 1
while left < right:
nodes[left].next = nodes[right]
left += 1
if left == right:
break
nodes[right].next = nodes[left]
right -= 1
nodes[left].next = None
Time O(n), space O(n). It passes the time limit β the constraint it violates is the follow-upβs O(1) space, and itβs the answer an interviewer will immediately ask you to improve.
Approach 2 β Middle + reverse + merge (optimal)
Insight: the target order is exactly first half interleaved with reversed second half. Each ingredient is a standard routine: the slow/fast (tortoise-and-hare) middle finder (fast advances two nodes per slowβs one, so slow stops midway), the in-place reversal (iteratively flip each next pointer), and an alternating splice of two lists. Composing them solves the problem with three O(n) sweeps and no storage.
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def reorderList(self, head: Optional[ListNode]) -> None:
# 1. find the middle: slow ends at the last node of the first half
slow, fast = head, head.next
while fast and fast.next:
slow = slow.next
fast = fast.next.next
# 2. detach and reverse the second half
second = slow.next
slow.next = None
prev = None
while second:
nxt = second.next
second.next = prev
prev = second
second = nxt
# 3. merge alternately: first half is never shorter than second
first, second = head, prev
while second:
f_next, s_next = first.next, second.next
first.next = second
second.next = f_next
first, second = f_next, s_next
Walkthrough on [1, 2, 3, 4, 5]:
- Middle: with
fast = head.next, slow stops at 3 β first half 1β2β3, second half 4β5.
- Detach and reverse: second half becomes
5β4.
- Merge, one row per loop iteration:
| first | second | list so far |
|---|
| 1 | 5 | 1β5β2β3 |
| 2 | 4 | 1β5β2β4β3 |
second is exhausted; the leftover middle node 3 is already correctly at the tail. Result [1, 5, 2, 4, 3].
Time O(n) β three linear passes. Space O(1) β a handful of pointers.
Starting fast at head.next makes slow stop at ceil(n/2), so for odd n the extra node stays in the first half β that is what lets the merge loop terminate purely on second.
Approach 3 β Recursion on the outer pair
Insight: the well-known alternative frames it as: reorder the sublist strictly inside head and the tail, then splice head β tail β (reordered inner part). Finding the tail each time costs O(n), giving O(nΒ²) unless you pass lengths cleverly; the commonly taught recursive variant uses the call stack to walk backward instead.
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def reorderList(self, head: Optional[ListNode]) -> None:
self.front = head
self.stop = False
def visit(node: Optional[ListNode]) -> None:
if not node:
return
visit(node.next) # unwind from the tail backward
if self.stop:
return
if self.front is node or self.front.next is node:
node.next = None # middle reached: terminate list
self.stop = True
return
nxt = self.front.next # weave: front -> node -> old front.next
self.front.next = node
node.next = nxt
self.front = nxt
visit(head)
Walkthrough on [1, 2, 3, 4]: the recursion bottoms out at 4 and unwinds. Visiting 4: weave after front 1 β 1β4β2β3, front becomes 2. Visiting 3: front.next is node (2βs next is 3) β set 3.next = None, stop. Result [1, 4, 2, 3].
Time O(n), space O(n) for the recursion stack β elegant, but 5Β·10^4 frames will overflow Pythonβs default recursion limit, so the iterative Approach 2 is the safe answer.
Common pitfalls
- Forgetting
slow.next = None before reversing β the first half then still runs into the second and the merge loops forever or duplicates nodes.
- Off-by-one in the middle finder:
fast = head.next vs fast = head changes which half gets the odd middle node; the merge termination condition must match your choice.
- Losing
first.next / second.next before overwriting them in the merge β save both, then splice.
- Rewriting node values from an array instead of relinking nodes β explicitly against the problem statement.
Pattern takeaway
Reorder List is the capstone of the linked-list trio: middle-finding, in-place reversal, and merging. None of the three is invented here β the skill is recognizing a scary-looking permutation as a composition of routines you already own. When a target order mixes βforward from the frontβ with βbackward from the backβ, think: split at the middle, reverse one side, merge.