Problem overview
The method reorderList receives head, the first node of a singly linked list with at least one node. The nodes in order are L0, L1, L2, ..., Ln. The method must re-link them so the list reads L0, Ln, L1, Ln-1, L2, Ln-2, and so on: first node, last node, second node, second to last, until every node is used once. It returns nothing, it changes only the next links (no values change and no nodes are created), and the list still starts at head.
The lesson provides the node type, ListNode, with an int val and a ListNode *next. For 1->2->3->4 the list becomes 1->4->2->3. For 1->2->3->4->5 it becomes 1->5->2->4->3, where the middle node 3 is left over and ends the list. A two-node list such as 4->8 stays as it is.
Approach: split, reverse the back half, interleave
The result takes nodes alternately from the front and from the back. A singly linked list can only be walked forward, so reaching the back nodes one by one is awkward. One fix is to copy the nodes into a vector and index from both ends, but that needs O(n) extra space. The goal here is O(n) time and O(1) extra space, so the nodes must be re-linked in place. That takes three passes:
- Find the middle and cut. Two pointers walk the list:
slowmoves one node at a time andfastmoves two. Whenfastcan't go further,slowis on the last node of the first half. Cutting after it splits1->2->3->4->5into1->2->3and4->5. - Reverse the second half.
4->5becomes5->4. Now the back nodes can be walked from the last node backwards. - Interleave. Take one node from each half in turn:
1->5->2->4->3.
With an odd count, the first half is one node longer, and its extra node is the middle one. It needs no moving, because after the interleaving it is already last.
Step by step
- Declare
slowandfastasListNode*, both set tohead. - Loop while
fast->next && fast->next->next: whilefasthas two more nodes ahead. A null pointer counts as false, so the two pointers are the whole condition. The first test also guards the second, because&&stops at the first false operand. - Advance
slowby one node andfastby two. The loop condition guaranteed that both of those nodes exist. - Save the second half. It starts at
slow->next; store that innode. - Cut the list with
slow->next = nullptr. Without the cut, the first half would still lead into the second, and the result would loop. - Start the reversal with
prevset tonullptr: the head of the already reversed part, empty at first. - Loop while
nodeis not null. Each round turns one node around. - Save
node->nextinnextNodebefore changing it, or the rest of the half is lost. - Point
node->nextatprev, then moveprevtonodeandnodetonextNode. - After the loop,
previs the head of the reversed half. Declarefirstasheadandsecondasprev. - Loop while
secondis not null. The second half is never longer than the first, so when it runs out every node has been placed. - Save
first->nextandsecond->nextinnextFirstandnextSecond, because the next two lines overwrite both links. - Link
first->nexttosecond, thensecond->nexttonextFirst. The back node now sits between two front nodes. - Move on:
firsttonextFirstandsecondtonextSecond. Nothing is returned, becauseheadis still the first node.
The solution in C++
class Solution {
public:
void reorderList(ListNode* head) {
ListNode* slow = head;
ListNode* fast = head;
while (fast->next && fast->next->next) {
slow = slow->next;
fast = fast->next->next;
}
ListNode* node = slow->next;
slow->next = nullptr;
ListNode* prev = nullptr;
while (node) {
ListNode* nextNode = node->next;
node->next = prev;
prev = node;
node = nextNode;
}
ListNode* first = head;
ListNode* second = prev;
while (second) {
ListNode* nextFirst = first->next;
ListNode* nextSecond = second->next;
first->next = second;
second->next = nextFirst;
first = nextFirst;
second = nextSecond;
}
}
};
Here is the middle search on two lists. On 1->2->3->4->5 the loop stops with slow at 3 and fast at 5. On 1->2->3->4 it stops with slow at 2 and fast at 3. So slow always ends on the last node of the first half, which is the larger half when the count is odd.
For 1->2->3->4->5 the state after each phase is:
after the cut: 1->2->3 and 4->5
after the reversal: 1->2->3 and 5->4
after the merge: 1->5->2->4->3
C++ details worth a closer look:
- Pointers in conditions. A null pointer converts to
falseand any other pointer totrue, sowhile (node)means "whilenodeis notnullptr". - Short-circuit
&&. Infast->next && fast->next->next, the right side is evaluated only whenfast->nextis non-null, so it never dereferences a null pointer. ->versus.. The variables hold addresses of nodes, so fields are reached with->. Assigningslow = slow->nextmoves the pointer, and assigningslow->next = nullptrchanges the node it points at.- No allocation. The method never calls
new. Every node that comes out is one that went in, which is the meaning of re-linking in place. - The pointer
headis not reassigned. The first node stays first, so the caller's pointer is still correct without passing it by reference.
Complexity
Time: O(n). Finding the middle visits about n/2 nodes with slow. The reversal visits the second half once, and the merge visits it once more. Each pass is linear and there are three of them.
Space: O(1). The method uses a fixed number of pointers (slow, fast, node, prev, nextNode, first, second, nextFirst, nextSecond) and no recursion, whatever the length of the list.
Edge cases and common mistakes
- One node. For
7, the loop does not run,slow->nextis alreadynullptr, the second half is empty, and nothing changes. - Two nodes.
4->8stays4->8. The second half is8, and linking it after 4 changes nothing. - Three nodes.
1->2->3becomes1->3->2. - Even and odd lengths. The loop condition leaves
slowon the 2nd node of 4 and the 3rd node of 5, so the first half is never shorter than the second. That is what lets the merge loop run onsecondalone. - Forgetting the cut. If
slow->nextkeeps pointing at the second half, the merged list ends in a cycle and printing it never stops. - Losing the rest of a list. In both the reversal and the merge, the next node must be saved before a link is overwritten.
- Returning a new head. The function returns nothing. The head is the same node before and after.
Testing
1->2->3->4gives1->4->2->3.1->2->3->4->5gives1->5->2->4->3.4->8gives4->8.7gives7.1->2->3gives1->3->2.1->2->3->4->5->6gives1->6->2->5->3->4.
