Problem overview
The function reorderList gets head, a pointer to the first ListNode of a singly linked list with at least one node (the lesson provides ListNode, with a Val and a Next pointer). It must rearrange the nodes from L0 -> L1 -> ... -> Ln into L0 -> Ln -> L1 -> Ln-1 -> L2 -> ...: the first node, then the last, then the second, then the second to last, and so on until every node is used once.
Only the links change. Values stay where they are, no nodes are created, and nothing is returned: the list still starts at head.
The list [1, 2, 3, 4] becomes [1, 4, 2, 3]. The list [1, 2, 3, 4, 5] becomes [1, 5, 2, 4, 3], with the middle node, 3, left over at the end. With two nodes, [4, 8], nothing moves.
Approach: split, reverse the back half, interleave
A list can't be walked backwards, so reaching the last node, then the second to last, is the hard part. Copying the nodes into a slice would make it easy, but that costs O(n) extra space, and the lesson asks for O(1). The alternative is to rearrange the list itself in three passes:
- Find the middle and cut the list there.
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. - Merge the halves, taking one node from each in turn.
1 -> 2 -> 3and5 -> 4give1 -> 5 -> 2 -> 4 -> 3.
Each pass visits each node a constant number of times, so the time is O(n), and only a few pointers are kept, so the extra space is O(1).
To find the middle, use two pointers: slow moves one node at a time and fast moves two. When fast can't move two more nodes, slow is on the last node of the first half. For four nodes, the loop stops with slow on 2 and fast on 3. For five nodes, it stops with slow on 3 and fast on 5. With an odd count, the middle node stays in the first half, and it is the node that ends the final list.
Step by step
Find the middle
- Create
slowandfast, both set tohead. - Loop while
fasthas two nodes after it:fast.Next != nil && fast.Next.Next != nil. Go has nowhile, so aforwith only a condition does the job. The first test comes first, sofast.Next.Nextis never read through a nil pointer. - Move
slowone node andfasttwo nodes in each round.
Cut and reverse the second half
- Remember the second half's start in
node := slow.Next. - Cut the list with
slow.Next = nil. The first half must end atslow. Without the cut, the merge produces a list that never ends: for[1, 2, 3, 4], following the links from the head gives1 4 2 3 3 3 3 3 ..., with the last node pointing at itself. - Declare
prevasvar prev *ListNode, the head of the reversed part, empty for now. Short declaration with:=can't be used here, because there is no value to take the type from. - Loop while
node != nil. Each round turns one node around: savenode.NextinnextNode(otherwise the rest of the half is lost), pointnode.Nextatprev, moveprevtonode, and movenodetonextNode.
Merge the halves
- Create
firstatheadandsecondatprev: the fronts of the first half and of the reversed second half. For1 -> 2 -> 3 -> 4 -> 5they are1 -> 2 -> 3and5 -> 4. - Loop while
second != nil. The second half is never longer than the first, so when it runs out, every node is placed, and a leftover first-half node is already at the end. - Save where each half continues:
nextFirst := first.NextandnextSecond := second.Next. - Link
secondafterfirstwithfirst.Next = second, then the rest of the first half aftersecondwithsecond.Next = nextFirst. - Advance both pointers to the saved nodes,
first = nextFirstandsecond = nextSecond.
The solution in Go
func reorderList(head *ListNode) {
slow := head
fast := head
for fast.Next != nil && fast.Next.Next != nil {
slow = slow.Next
fast = fast.Next.Next
}
node := slow.Next
slow.Next = nil
var prev *ListNode
for node != nil {
nextNode := node.Next
node.Next = prev
prev = node
node = nextNode
}
first := head
second := prev
for second != nil {
nextFirst := first.Next
nextSecond := second.Next
first.Next = second
second.Next = nextFirst
first = nextFirst
second = nextSecond
}
}
Go details worth knowing:
- Pointers are the list.
headis a*ListNode, andnode.Next = prevchanges the node thatnodepoints to, which is why the function can reorder the caller's list without returning anything. - Short-circuit
&&. In the loop condition,fast.Next.Nextis read only whenfast.Nextisn'tnil. Swapping the two tests would panic on lists of odd length. - The zero value of a pointer is
nil.var prev *ListNodeis exactly the empty reversed list, and the first node turned around getsnilas itsNext. - Parallel updates in the merge. Both next nodes are saved before either link changes, because
first.Nextandsecond.Nextare both overwritten right after. - The list must have a node. The constraints promise at least one. A
nilhead would panic atfast.Nextwith a nil pointer dereference.
Complexity
Time: O(n). Finding the middle takes about n / 2 rounds, reversing the second half takes about n / 2 rounds, and the merge takes about n / 2 rounds. Each round does a constant amount of work.
Space: O(1). The function uses a fixed number of pointers (slow, fast, node, prev, first, second and the saved next nodes), and no recursion.
Edge cases and common mistakes
- One node.
[7]stays[7]. The middle loop doesn't run,nodeisnil, nothing is reversed, andsecondisnilso the merge doesn't run. - Two nodes.
[4, 8]stays[4, 8].slowstays on the first node, the second node is split off and linked straight back after it. - Three nodes.
[1, 2, 3]becomes[1, 3, 2]. - Even length.
[1, 2, 3, 4, 5, 6]becomes[1, 6, 2, 5, 3, 4]: the halves have the same length, and the merge ends with the last node of the first half. - Forgetting the cut. The first half still points into the second half, and the merged list ends in a cycle, as shown above.
- Reversing before saving
node.Next. Afternode.Next = prev, the rest of the half can no longer be reached.
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].
