143. Reorder List
Problem Description
You are given the head of a singly linked list. The task is to reorder the list by alternating nodes from the beginning and end of the list.
Given a linked list with nodes L0 → L1 → L2 → ... → Ln-1 → Ln
, you need to rearrange it to follow this pattern: L0 → Ln → L1 → Ln-1 → L2 → Ln-2 → ...
In other words:
- Take the first node from the start (
L0
) - Then take the last node (
Ln
) - Then take the second node from the start (
L1
) - Then take the second-to-last node (
Ln-1
) - Continue this pattern until all nodes are processed
The reordering must be done in-place by changing the node connections. You cannot modify the values stored in the nodes - only the links between nodes can be changed.
For example:
- Input:
1 → 2 → 3 → 4
becomes1 → 4 → 2 → 3
- Input:
1 → 2 → 3 → 4 → 5
becomes1 → 5 → 2 → 4 → 3
The solution approach involves three main steps:
- Find the middle of the list using fast and slow pointers - the slow pointer moves one step while the fast pointer moves two steps
- Reverse the second half of the list starting from the node after the middle
- Merge the two halves by alternating nodes from the first half and the reversed second half
Intuition
Looking at the reordering pattern, we need to alternate between nodes from the start and nodes from the end of the list. The challenge is that in a singly linked list, we can only traverse forward - we can't easily access nodes from the end.
The key insight is to observe what's happening: we're essentially weaving together two sequences - the first half of the list going forward (L0, L1, L2...
) and the second half going backward (Ln, Ln-1, Ln-2...
).
If we could somehow have two separate lists - one containing the first half and another containing the second half in reverse order - then we could simply merge them by alternating nodes. This transforms our problem into three simpler subproblems:
-
Split the list in half: We need to find where to split. Using the fast and slow pointer technique is perfect here - when the fast pointer reaches the end (moving 2 steps at a time), the slow pointer (moving 1 step at a time) will be at the middle.
-
Reverse the second half: Once we have the second half separated, we need to reverse it so that we can access what were originally the last nodes first. This way,
Ln-1 → Ln
becomesLn → Ln-1
. -
Merge alternately: With the first half as
L0 → L1 → L2...
and the reversed second half asLn → Ln-1 → Ln-2...
, we can now easily alternate between them to create the desired pattern.
This approach is elegant because it breaks down a complex reordering problem into three well-known, simple operations on linked lists. Each step has O(n)
time complexity and uses O(1)
extra space, making the entire solution efficient.
Solution Approach
Let's walk through the implementation step by step, following the three-phase approach we identified:
Phase 1: Find the Middle Using Fast and Slow Pointers
fast = slow = head
while fast.next and fast.next.next:
slow = slow.next
fast = fast.next.next
We initialize both fast
and slow
pointers at the head. The fast pointer moves two steps at a time while the slow pointer moves one step. When the fast pointer reaches the end, the slow pointer will be at the middle. The condition fast.next and fast.next.next
ensures we don't get null pointer errors.
For a list of length n
:
- If
n
is even, slow stops at positionn/2 - 1
- If
n
is odd, slow stops at positionn/2
(the middle element)
Phase 2: Split and Reverse the Second Half
cur = slow.next
slow.next = None
First, we save the start of the second half in cur
and disconnect it from the first half by setting slow.next = None
.
pre = None
while cur:
t = cur.next
cur.next = pre
pre, cur = cur, t
Now we reverse the second half using the standard reversal technique:
- Save the next node in temporary variable
t
- Point current node backward to
pre
- Move
pre
andcur
forward
After this loop, pre
points to the head of the reversed second half.
Phase 3: Merge the Two Halves Alternately
cur = head
while pre:
t = pre.next
pre.next = cur.next
cur.next = pre
cur, pre = pre.next, t
Starting from the head of the first half (cur = head
), we weave the nodes:
- Save the next node from the second half in
t
- Insert the node from the second half (
pre
) after the current node from the first half - Connect
pre
to what was originally aftercur
- Move forward:
cur
moves to what is now two positions ahead,pre
moves to the saved next node
The merging continues until all nodes from the second half are processed (while pre
). Since the second half has at most the same number of nodes as the first half (or one less), this ensures all nodes are properly reordered.
Time and Space Complexity:
- Time:
O(n)
- we traverse the list a constant number of times - Space:
O(1)
- only using a few pointer variables, no extra data structures
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with the list: 1 → 2 → 3 → 4 → 5
Initial State:
1 → 2 → 3 → 4 → 5
Phase 1: Find the Middle
We use two pointers - slow
(moves 1 step) and fast
(moves 2 steps):
- Start:
slow = 1
,fast = 1
- Step 1:
slow = 2
,fast = 3
- Step 2:
slow = 3
,fast = 5
- Stop (fast.next is null)
The slow pointer is now at node 3
, which is our middle point.
Phase 2: Split and Reverse Second Half
First, we split at the middle:
First half: 1 → 2 → 3 → null Second half: 4 → 5 → null
Now reverse the second half (4 → 5
):
- Initially:
pre = null
,cur = 4
- Step 1: Make
4
point tonull
, move forward- Result:
4 → null
,pre = 4
,cur = 5
- Result:
- Step 2: Make
5
point to4
, move forward- Result:
5 → 4 → null
,pre = 5
,cur = null
- Result:
Reversed second half: 5 → 4 → null
Phase 3: Merge Alternately
Now we have:
- First half:
1 → 2 → 3
- Second half (reversed):
5 → 4
Merging process:
- Start:
cur = 1
,pre = 5
- Step 1: Insert
5
after1
- Result:
1 → 5 → 2 → 3
, movecur
to2
,pre
to4
- Result:
- Step 2: Insert
4
after2
- Result:
1 → 5 → 2 → 4 → 3
, movecur
to3
,pre
tonull
- Result:
- Stop (pre is null)
Final Result: 1 → 5 → 2 → 4 → 3
Each phase operates in linear time with constant space, making this approach both time and space efficient.
Solution Implementation
1# Definition for singly-linked list.
2# class ListNode:
3# def __init__(self, val=0, next=None):
4# self.val = val
5# self.next = next
6
7class Solution:
8 def reorderList(self, head: Optional[ListNode]) -> None:
9 """
10 Reorders the linked list in-place from L0->L1->...->Ln to L0->Ln->L1->Ln-1->...
11
12 Algorithm:
13 1. Find the middle of the linked list using two pointers
14 2. Split the list into two halves
15 3. Reverse the second half
16 4. Merge the two halves by alternating nodes
17 """
18
19 # Step 1: Find the middle node using fast and slow pointers
20 slow_pointer = fast_pointer = head
21 while fast_pointer.next and fast_pointer.next.next:
22 slow_pointer = slow_pointer.next
23 fast_pointer = fast_pointer.next.next
24
25 # Step 2: Split the list into two halves
26 # slow_pointer now points to the middle node
27 second_half_head = slow_pointer.next
28 slow_pointer.next = None # Disconnect first half from second half
29
30 # Step 3: Reverse the second half of the list
31 previous_node = None
32 current_node = second_half_head
33 while current_node:
34 next_temp = current_node.next
35 current_node.next = previous_node
36 previous_node = current_node
37 current_node = next_temp
38
39 # previous_node now points to the head of reversed second half
40 reversed_second_half = previous_node
41 first_half = head
42
43 # Step 4: Merge the two halves by alternating nodes
44 # Take one node from first half, then one from reversed second half
45 while reversed_second_half:
46 # Save next nodes
47 first_half_next = first_half.next
48 second_half_next = reversed_second_half.next
49
50 # Connect nodes alternately
51 first_half.next = reversed_second_half
52 reversed_second_half.next = first_half_next
53
54 # Move pointers forward
55 first_half = first_half_next
56 reversed_second_half = second_half_next
57
1/**
2 * Definition for singly-linked list.
3 * public class ListNode {
4 * int val;
5 * ListNode next;
6 * ListNode() {}
7 * ListNode(int val) { this.val = val; }
8 * ListNode(int val, ListNode next) { this.val = val; this.next = next; }
9 * }
10 */
11class Solution {
12 public void reorderList(ListNode head) {
13 // Step 1: Find the middle of the linked list using two pointers
14 // slow pointer moves one step at a time, fast pointer moves two steps
15 ListNode slowPointer = head;
16 ListNode fastPointer = head;
17
18 while (fastPointer.next != null && fastPointer.next.next != null) {
19 slowPointer = slowPointer.next;
20 fastPointer = fastPointer.next.next;
21 }
22
23 // Step 2: Split the list into two halves
24 // The second half starts from slowPointer.next
25 ListNode secondHalfHead = slowPointer.next;
26 slowPointer.next = null; // Disconnect the first half from the second half
27
28 // Step 3: Reverse the second half of the list
29 ListNode previousNode = null;
30 ListNode currentNode = secondHalfHead;
31
32 while (currentNode != null) {
33 ListNode nextTemp = currentNode.next; // Store next node
34 currentNode.next = previousNode; // Reverse the pointer
35 previousNode = currentNode; // Move previous to current
36 currentNode = nextTemp; // Move to next node
37 }
38
39 // Step 4: Merge the two halves by alternating nodes
40 // previousNode now points to the head of the reversed second half
41 ListNode firstHalfCurrent = head;
42 ListNode secondHalfCurrent = previousNode;
43
44 while (secondHalfCurrent != null) {
45 // Store next nodes before modifying pointers
46 ListNode firstHalfNext = firstHalfCurrent.next;
47 ListNode secondHalfNext = secondHalfCurrent.next;
48
49 // Insert node from second half after current node in first half
50 firstHalfCurrent.next = secondHalfCurrent;
51 secondHalfCurrent.next = firstHalfNext;
52
53 // Move pointers forward
54 firstHalfCurrent = firstHalfNext;
55 secondHalfCurrent = secondHalfNext;
56 }
57 }
58}
59
1/**
2 * Definition for singly-linked list.
3 * struct ListNode {
4 * int val;
5 * ListNode *next;
6 * ListNode() : val(0), next(nullptr) {}
7 * ListNode(int x) : val(x), next(nullptr) {}
8 * ListNode(int x, ListNode *next) : val(x), next(next) {}
9 * };
10 */
11class Solution {
12public:
13 void reorderList(ListNode* head) {
14 // Edge case: empty list or single node
15 if (!head || !head->next) {
16 return;
17 }
18
19 // Step 1: Find the middle of the linked list using two pointers
20 ListNode* fastPtr = head;
21 ListNode* slowPtr = head;
22
23 // Move fast pointer twice as fast as slow pointer
24 // When fast reaches end, slow will be at middle
25 while (fastPtr->next && fastPtr->next->next) {
26 slowPtr = slowPtr->next;
27 fastPtr = fastPtr->next->next;
28 }
29
30 // Step 2: Split the list into two halves
31 // Second half starts from slowPtr->next
32 ListNode* secondHalfHead = slowPtr->next;
33 slowPtr->next = nullptr; // Disconnect first half from second half
34
35 // Step 3: Reverse the second half of the list
36 ListNode* previous = nullptr;
37 ListNode* current = secondHalfHead;
38
39 while (current) {
40 ListNode* nextTemp = current->next; // Store next node
41 current->next = previous; // Reverse the link
42 previous = current; // Move previous forward
43 current = nextTemp; // Move current forward
44 }
45
46 // Step 4: Merge the two halves alternately
47 // previous now points to the head of reversed second half
48 ListNode* firstHalfCurrent = head;
49 ListNode* secondHalfCurrent = previous;
50
51 while (secondHalfCurrent) {
52 // Store next nodes to avoid losing references
53 ListNode* firstHalfNext = firstHalfCurrent->next;
54 ListNode* secondHalfNext = secondHalfCurrent->next;
55
56 // Interweave nodes: first -> second -> original first's next
57 firstHalfCurrent->next = secondHalfCurrent;
58 secondHalfCurrent->next = firstHalfNext;
59
60 // Move pointers forward
61 firstHalfCurrent = firstHalfNext;
62 secondHalfCurrent = secondHalfNext;
63 }
64 }
65};
66
1/**
2 * Definition for singly-linked list.
3 * class ListNode {
4 * val: number
5 * next: ListNode | null
6 * constructor(val?: number, next?: ListNode | null) {
7 * this.val = (val===undefined ? 0 : val)
8 * this.next = (next===undefined ? null : next)
9 * }
10 * }
11 */
12
13/**
14 * Reorders a linked list by interleaving nodes from the first half with nodes from the reversed second half.
15 * Pattern: L0 → L1 → ... → Ln-1 → Ln becomes L0 → Ln → L1 → Ln-1 → L2 → Ln-2 → ...
16 * Do not return anything, modify head in-place instead.
17 */
18function reorderList(head: ListNode | null): void {
19 if (!head || !head.next) {
20 return;
21 }
22
23 // Step 1: Find the middle of the linked list using two pointers
24 let slowPointer: ListNode | null = head;
25 let fastPointer: ListNode | null = head;
26
27 while (fastPointer && fastPointer.next) {
28 slowPointer = slowPointer.next!;
29 fastPointer = fastPointer.next.next;
30 }
31
32 // Step 2: Reverse the second half of the linked list
33 // Split the list into two halves
34 let secondHalfHead: ListNode | null = slowPointer.next;
35 slowPointer.next = null; // Disconnect first half from second half
36
37 // Reverse the second half
38 let reversedHead: ListNode | null = null;
39 while (secondHalfHead) {
40 const nextNode: ListNode | null = secondHalfHead.next;
41 secondHalfHead.next = reversedHead;
42 reversedHead = secondHalfHead;
43 secondHalfHead = nextNode;
44 }
45
46 // Step 3: Merge the two halves by alternating nodes
47 let firstHalfPointer: ListNode | null = head;
48 let secondHalfPointer: ListNode | null = reversedHead;
49
50 while (secondHalfPointer) {
51 // Save next nodes
52 const firstHalfNext: ListNode | null = firstHalfPointer.next;
53 const secondHalfNext: ListNode | null = secondHalfPointer.next;
54
55 // Interleave nodes
56 firstHalfPointer.next = secondHalfPointer;
57 secondHalfPointer.next = firstHalfNext;
58
59 // Move pointers forward
60 firstHalfPointer = firstHalfNext!;
61 secondHalfPointer = secondHalfNext;
62 }
63}
64
Time and Space Complexity
The time complexity is O(n)
, where n
is the length of the linked list. This is because:
- The first while loop finds the middle of the list using two pointers (fast and slow), which takes
O(n/2)
=O(n)
time - The second while loop reverses the second half of the list, which takes
O(n/2)
=O(n)
time - The third while loop merges the two halves by alternating nodes, which takes
O(n/2)
=O(n)
time - Overall:
O(n) + O(n) + O(n)
=O(n)
The space complexity is O(1)
. The algorithm only uses a constant amount of extra space for pointer variables (fast
, slow
, cur
, pre
, t
), regardless of the input size. No additional data structures like arrays or new linked list nodes are created.
Common Pitfalls
1. Incorrect Middle Point Detection for Even-Length Lists
One of the most common mistakes is misunderstanding where the slow pointer ends up in even-length lists. The condition while fast.next and fast.next.next:
causes the slow pointer to stop at different relative positions depending on the list length.
The Problem:
For a list like 1 → 2 → 3 → 4
, developers often assume the split will be symmetric (2 nodes each), but the algorithm actually splits it as:
- First half:
1 → 2
- Second half:
3 → 4
If you use while fast and fast.next:
instead, the slow pointer would advance one more position, giving:
- First half:
1 → 2 → 3
- Second half:
4
This asymmetric split would break the merging logic since the first half would have more than one extra node.
Solution:
Stick with while fast.next and fast.next.next:
to ensure the first half has at most one more node than the second half. This guarantees the merge loop works correctly.
2. Forgetting to Disconnect the Two Halves
The Problem:
Omitting the line slow.next = None
creates a cycle in the list. Without disconnecting, the first half still points to the second half, which gets reversed. This creates a circular reference that leads to infinite loops during merging.
For example, without disconnection in list 1 → 2 → 3 → 4
:
- After reversal, you'd have:
1 → 2 → 3
and3
also points back via the reversed chain - This creates a cycle that breaks the merge logic
Solution:
Always remember to set slow.next = None
immediately after identifying the start of the second half. This cleanly separates the two halves before reversal.
3. Incorrect Merge Pointer Movement
The Problem: A subtle but critical error occurs in the merge step with this line:
cur, pre = pre.next, t
Developers might mistakenly write:
cur = cur.next # Wrong!
pre = t
The issue is that after inserting pre
between cur
and cur.next
, the original cur.next
is now at pre.next
. Moving cur
to its original next would skip the newly inserted node, causing incorrect ordering.
Solution:
Use cur = pre.next
(or the combined assignment shown) to correctly advance to what was originally the next node in the first half, which is now two positions ahead after the insertion.
4. Edge Case: Single Node or Empty List
The Problem: The algorithm assumes at least two nodes exist. For a single node or empty list, the fast/slow pointer logic might fail or be unnecessary.
Solution: Add an early return check:
if not head or not head.next:
return
This handles both empty lists and single-node lists efficiently without running through unnecessary steps.
Which of the following is a good use case for backtracking?
Recommended Readings
Stack Intro Imagine you have a pile of books on your desk If you want to add a new book you place it on top If you want to read a book you take it from the top And if you simply want to see which book is on the top you
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Linked List Cycle Given a linked list with potentially a loop determine whether the linked list from the first node contains a cycle in it For bonus points do this with constant space Parameters nodes The first node of a linked list with potentially a loop Result Whether there is a loop contained
Want a Structured Path to Master System Design Too? Don’t Miss This!