Problem overview
The method reorderList(head) receives the first node of a singly linked list with at least one node. It must rearrange the list in place so that the nodes read first, last, second, second to last, and so on, until every node has been used once. The list still starts at head, and the method returns nothing. Only the links between nodes may change: values stay where they are and no new nodes are created.
The lesson provides the node class, shown in a comment in the starter code: a ListNode with a val and a next that is None at the end of the list.
For 1 -> 2 -> 3 -> 4 the result is 1 -> 4 -> 2 -> 3. For 1 -> 2 -> 3 -> 4 -> 5 it is 1 -> 5 -> 2 -> 4 -> 3: the middle node, 3, is left over and ends the list. With two nodes, 4 -> 8, nothing moves, since the last node already follows the first.
Approach: split, reverse, interleave
A singly linked list can only be walked forward, so reaching the last node, then the second to last, directly isn't possible. An easy way around that is to copy the nodes into a Python list and index it from both ends, but that costs O(n) extra space. The goal here is O(n) time with O(1) extra space, and it takes three passes:
- Find the middle with two pointers, a slow one that moves one node at a time and a fast one that moves two, and cut the list after the middle.
1 -> 2 -> 3 -> 4 -> 5becomes1 -> 2 -> 3and4 -> 5. - Reverse the second half.
4 -> 5becomes5 -> 4. Now the back of the list can be walked from its front. - Take one node from each half in turn:
1 -> 5 -> 2 -> 4 -> 3.
With an even count the halves have the same length. With an odd count the first half is one node longer and keeps the middle node, which ends up last, exactly where the answer needs it.
Step by step
Finding the middle
- Start both pointers at
head. Createslowandfast. - Loop while
fast.next and fast.next.next. The loop continues only iffasthas two more nodes after it, so both steps below are safe.Noneis falsy, so no explicit comparison is needed. Stopping here leavesslowon the last node of the first half: the 2nd of 4 nodes, the 3rd of 5. - Move
slowone node andfasttwo nodes each round. - Remember the second half. It starts right after
slow, so storeslow.nextinnode. - Cut the list: set
slow.next = None. Without the cut,slowwould still point into the second half, and the final list would loop back on itself. For1 -> 2 -> 3 -> 4the uncut version ends in 3 pointing to itself, so printing it never finishes: it reads 1, 4, 2, 3, 3, 3, 3, and so on.
Reversing the second half
- Create
prev = None. It is the head of the part already reversed, empty at first. - Loop while
nodeis notNone. Each round turns one node around. - Save
node.nextinnextNode. The next line overwritesnode.next, and without the saved copy the rest of the half would be lost. - Point
node.nextatprev, then moveprevtonode, then movenodetonextNode.
Interleaving the halves
- Create
first = headandsecond = prev, the heads of the first half and the reversed second half. For1 -> 2 -> 3 -> 4 -> 5these are1 -> 2 -> 3and5 -> 4. - Loop while
secondis notNone. The second half is never longer than the first, so when it runs out every node is placed, and a leftover middle node is already at the end. - Save where each half continues:
first.nextinnextFirstandsecond.nextinnextSecond. - Link
first.nexttosecond, thensecond.nexttonextFirst, so the node from the back sits between two nodes from the front. - Advance:
firsttonextFirstandsecondtonextSecond.
The solution in Python
class Solution:
def reorderList(self, head: ListNode) -> None:
slow = head
fast = head
while fast.next and fast.next.next:
slow = slow.next
fast = fast.next.next
node = slow.next
slow.next = None
prev = None
while node:
nextNode = node.next
node.next = prev
prev = node
node = nextNode
first = head
second = prev
while second:
nextFirst = first.next
nextSecond = second.next
first.next = second
second.next = nextFirst
first = nextFirst
second = nextSecond
Notes on the Python side:
- The method returns
None. It has noreturn, so Python returnsNoneby itself. The caller keeps usinghead, which is still the first node. - Variables are references.
slow,fast,nodeand the rest are names for the same node objects, not copies, soslow.next = Nonereally cuts the list. - Short-circuit
and. Infast.next and fast.next.next, the second part is evaluated only iffast.nextis truthy. That prevents anAttributeErrorfrom reading.nextonNone. - Assignment order matters. In the loops, each line reads values saved by earlier lines. Reordering the lines would lose nodes.
Complexity
Each pass visits each node a constant number of times: finding the middle walks about half the list, reversing walks the second half, and interleaving walks the second half again. The total time is O(n). Only a handful of pointers are used, so the extra space is O(1).
Edge cases and common mistakes
- One node. The loop condition fails immediately,
slow.nextisNone, the second half is empty, and nothing changes.[7]stays[7]. - Two nodes.
[4, 8]stays[4, 8].slowstays on the first node, the second half is just8, and linking it after4reproduces the same list. - Three nodes.
[1, 2, 3]becomes[1, 3, 2]. - Forgetting the cut. The list ends in a cycle, as described above.
- Losing the rest of a half by overwriting a
nextlink before saving it.
Testing
| list | after reorderList |
|---|---|
[1, 2, 3, 4] | [1, 4, 2, 3] |
[1, 2, 3, 4, 5] | [1, 5, 2, 4, 3] |
[4, 8] | [4, 8] |
[7] | [7] |
[1, 2, 3] | [1, 3, 2] |
