Problem overview
The method reorderList(_ head: ListNode?) receives the head of a singly linked list L0 -> L1 -> ... -> Ln and rearranges it, in place, into L0 -> Ln -> L1 -> Ln-1 -> L2 -> ...: first node, last node, second node, second to last, and so on. Only the links may change. The values stay where they are in their nodes, no node is created, and nothing is returned: the list still starts at head. The lesson provides ListNode, a class with a val and an optional next.
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], with the middle node 3 left over at the end. For [4, 8] nothing changes, because the last node already follows the first.
Approach: three passes with O(1) extra space
Copying the nodes into an array would make this easy, but it uses O(n) extra space. The in-place idea splits the job into three passes, each a classic list technique:
- 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 one reaches the end, the slow one is in the middle. Cutting after it gives two lists:
1 -> 2 -> 3and4 -> 5. - Reverse the second half.
4 -> 5becomes5 -> 4. Now walking the second half from its front visits the nodes in the order the answer needs: last, second to last, and so on. - Merge the halves by alternating: one node from the first half, then one from the second.
Each pass touches each node a constant number of times, so the total time is O(n).
Step by step
- Create the two pointers.
headis aListNode?, but the list has at least one node, sohead!unwraps it. Declareslowandfastwithvarand set both tohead!; they are plainListNodevalues. - Loop while
fasthas two more nodes ahead:fast.next != nil && fast.next!.next != nil. The first test makes the!in the second safe. - Move
slowone node toslow.next!. - Move
fasttwo nodes tofast.next!.next!. The loop condition guaranteed both exist. - Remember the second half:
var node = slow.next. With an odd number of nodes, the middle stays at the end of the first half. - Cut the list with
slow.next = nil. Without the cut, the first half would still lead into the second, and the merged list would loop. - Start the reversal.
var prev: ListNode? = nilis the head of the part already reversed, empty at first. The type is written out becausenilalone doesn't say what kind of optional it is. - Loop while
node != nil, turning one node around per round. - Save the next node first:
let nextNode = node!.next. The next line overwritesnode!.next, and without the copy the rest of the half would be lost. - Point
node!.nextback atprev, which reverses that link. - Move
prevtonode: it is now the head of the reversed part. - Move
nodetonextNode, the next node to turn. - Prepare the merge. After the loop
prevheads the reversed second half. Setfirsttoheadandsecondtoprev, both withvar. - 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 node of the first half already sits at the end. - Save where each half continues:
nextFirstandnextSecond, bothlet. - Link
secondafterfirst:first!.next = second. - Link the rest of the first half after
second:second!.next = nextFirst. - Advance both pointers to
nextFirstandnextSecond.
A trace on five nodes
For [1, 2, 3, 4, 5], the first loop ends with slow on 3 and fast on 5. For [1, 2, 3, 4], it ends with slow on 2 and fast on 3. So the first half is the larger one when the count is odd.
After the cut, the five-node list is split into [1, 2, 3] and [4, 5]. After the reversal loop, the halves are [1, 2, 3] and [5, 4]. The merge then produces [1, 5, 2, 4, 3].
The solution in Swift
class Solution {
func reorderList(_ head: ListNode?) {
var slow = head!
var fast = head!
while fast.next != nil && fast.next!.next != nil {
slow = slow.next!
fast = fast.next!.next!
}
var node = slow.next
slow.next = nil
var prev: ListNode? = nil
while node != nil {
let nextNode = node!.next
node!.next = prev
prev = node
node = nextNode
}
var first = head
var second = prev
while second != nil {
let nextFirst = first!.next
let nextSecond = second!.next
first!.next = second
second!.next = nextFirst
first = nextFirst
second = nextSecond
}
}
}
The Swift details worth a closer look:
- A constant parameter, a mutable list.
headis aletparameter, yet the function changes the list.ListNodeis a class, a reference type, so the parameter holds a reference, and changingnexton a node changes the node itself. Only reassigningheadwould be forbidden, and the function never needs to. - Why
headis still the head. The first node never moves: the first node is always the first in the result. Nothing has to be returned. - Optionals and
!.slowandfastare non-optional because they start fromhead!and only move to nodes known to exist.node,prev,firstandsecondstay optional, since they must be able to becomenil. Each!is guarded by the loop condition, and force-unwrapping anilwould crash the program. - Comparing with
nil.node != nilworks even thoughListNodehas no equality of its own: Swift compares any optional withnildirectly.
Complexity
Time: O(n). Finding the middle visits about half the nodes, the reversal visits the second half once, and the merge visits each node once.
Space: O(1). The function uses a handful of pointers and creates no nodes. The nodes are class instances, so relinking them allocates nothing.
Edge cases and common mistakes
- One node.
[7]stays[7]:fast.nextisnil, the second half is empty, and the merge loop never runs. - Two nodes.
[4, 8]stays[4, 8]. - Three nodes.
[1, 2, 3]becomes[1, 3, 2]. - Odd versus even length. For six nodes the result is
[1, 6, 2, 5, 3, 4]. With an odd length, the middle node ends the list. - Forgetting the cut. Without
slow.next = nil, the first half still points into the second, and the merged list gets a cycle. - Losing the rest while reversing. Overwriting
nextbefore saving it throws away the remainder of the list. - A loop condition without the second test. Checking only
fast.next != nilwould letfast.next!.next!unwrap anilon lists with an even length, and crash.
Testing
[1, 2, 3, 4]becomes[1, 4, 2, 3].[1, 2, 3, 4, 5]becomes[1, 5, 2, 4, 3].[4, 8]becomes[4, 8].[7]becomes[7].[1, 2, 3]becomes[1, 3, 2].[1, 2, 3, 4, 5, 6]becomes[1, 6, 2, 5, 3, 4].
