Problem overview
The method reorderList(head) receives the first node of a singly linked list with at least one node. The nodes are L0 -> L1 -> ... -> Ln, and the list must become L0 -> Ln -> L1 -> Ln-1 -> L2 -> ...: the first node, the last, the second, the second to last, and so on. Only the links between nodes change. Values stay where they are, no new nodes are created, and nothing is returned, because the list still starts at head. The lesson provides the node type, a ListNode with val and next fields.
The list [1, 2, 3, 4] becomes [1, 4, 2, 3], and [1, 2, 3, 4, 5] becomes [1, 5, 2, 4, 3]. The middle node, 3, is what remains at the end. With two nodes, [4, 8], nothing moves.
Approach: split, reverse, merge
Copying the nodes into an array would make this easy, because an array can be read from both ends, but it needs O(n) extra space. A singly linked list can only be walked forward, so the lesson rearranges the links in three passes and uses O(1) extra space.
- Find the middle and cut the list there. A slow pointer moves one node at a time and a fast pointer two. When the fast pointer can't go further, the slow one is at the end of the first half.
1 -> 2 -> 3 -> 4 -> 5becomes1 -> 2 -> 3and4 -> 5. - Reverse the second half.
4 -> 5becomes5 -> 4. Now the nodes the answer needs from the back (Ln, then Ln-1) can be read from the front. - Merge by alternating. Take a node from the first half, then one from the reversed second half, and repeat:
1 -> 5 -> 2 -> 4 -> 3.
Each pass visits every node at most once, so the total time is O(n).
Step by step
Find the middle
- Create
slowandfastwithletand start both athead. - Loop while
fast.next !== nullandfast.next.next !== null, sofastcan always take two steps. - In the loop, move
slowtoslow.nextandfasttofast.next.next.
The loop stops with slow on the last node of the first half: the 2nd of 4 nodes, the 3rd of 5. With an odd count the middle node stays in the first half, which is how it ends up last in the result.
Cut and reverse the second half
- Store
slow.next, the start of the second half, innode. - Set
slow.nexttonull. Without this cut, the first half would still lead into the second, and the merged list would loop. - Create
prevasnull: the head of the part reversed so far, which is empty at first. - While
nodeis notnull: savenode.nextinnextNode, pointnode.nextatprev, moveprevtonode, and movenodetonextNode. Saving the next node first matters, because the second line overwrites the only link to the rest of the half.
Merge
- Start
firstatheadandsecondatprev, the head of the reversed half. - While
secondis notnull: savefirst.nextinnextFirstandsecond.nextinnextSecond. - Set
first.nexttosecond, thensecond.nexttonextFirst. The node from the back now sits between two nodes from the front. - Move
firsttonextFirstandsecondtonextSecond.
The second half is never longer than the first, so when it runs out every node is placed. A node left over in the first half is already at the end.
The solution in JavaScript
class Solution {
reorderList(head) {
let slow = head;
let fast = head;
while (fast.next !== null && fast.next.next !== null) {
slow = slow.next;
fast = fast.next.next;
}
let node = slow.next;
slow.next = null;
let prev = null;
while (node !== null) {
const nextNode = node.next;
node.next = prev;
prev = node;
node = nextNode;
}
let first = head;
let second = prev;
while (second !== null) {
const nextFirst = first.next;
const nextSecond = second.next;
first.next = second;
second.next = nextFirst;
first = nextFirst;
second = nextSecond;
}
}
}
The JavaScript details worth knowing:
- No return value. The method returns
undefined. It works because nodes are objects, andheadrefers to the same node object before and after. Changingfirst.nextchanges the node itself, not a copy. !== null. Links in this list end withnull(ListNodesetsnexttonullwhen it isn't given one). Comparing with!==checks for exactlynullwithout any conversion.- Order of the assignments. In both loops, the next node is saved in a
constbefore a link is overwritten. Changing the order loses the rest of the list. letandconst. Pointers that move (slow,fast,node,prev,first,second) uselet. The saved links, assigned once per round, useconst.
Complexity
Time: O(n). Finding the middle takes about n / 2 steps of slow, reversing the second half takes about n / 2 steps, and the merge takes about n / 2 rounds.
Space: O(1). The method uses a fixed number of pointers and creates no nodes.
Edge cases and common mistakes
- One node. For
[7]the loop condition fails immediately,slow.nextisnull, the reversed half is empty, and the merge loop never runs. The list stays[7]. - Two nodes.
[4, 8]stays[4, 8]: the second half is just8, and linking it after4reproduces the same list. - Three nodes.
[1, 2, 3]becomes[1, 3, 2]. - Even and odd lengths.
[1, 2, 3, 4, 5, 6]becomes[1, 6, 2, 5, 3, 4]. For 4 nodesslowstops on the 2nd node and for 5 nodes on the 3rd, so the first half is never shorter than the second. - Forgetting
slow.next = null. The last node of the first half still points into the second half, so the merged list contains a cycle. - Reversing without saving
node.next. Afternode.next = prevthe rest of the half is unreachable. - Using values instead of links. Writing the values into the nodes in a new order breaks the rule that only links change.
Testing
[1, 2, 3, 4]gives[1, 4, 2, 3].[1, 2, 3, 4, 5]gives[1, 5, 2, 4, 3].[4, 8]gives[4, 8].[7]gives[7].[1, 2, 3]gives[1, 3, 2].[1, 2, 3, 4, 5, 6]gives[1, 6, 2, 5, 3, 4].
