Sort List: Merge Sort on a Linked List
Sort List is LeetCode problem 148. It asks you to sort a linked list in ascending order, ideally in O(n log n) time. The natural choice is merge sort, and it maps beautifully onto linked lists because splitting and merging are both pointer operations with no random access needed.
If you already know how to find the middle of a linked list and how to merge two sorted lists, this problem is just combining those two building blocks inside a recursive framework.
The problem
Given the head of a linked list, return the list after sorting it in ascending order.
Example: [4, 2, 1, 3] becomes [1, 2, 3, 4]. Another example: [-1, 5, 3, 4, 0] becomes [-1, 0, 3, 4, 5].
Follow up: Can you sort the linked list in O(n log n) time and O(1) memory (i.e. constant space)?
The approach: merge sort
Merge sort works by splitting the input in half, recursively sorting each half, and merging the two sorted halves. On arrays you need O(n) extra space for the merge buffer. On linked lists, the merge is done in-place by rearranging pointers, so no extra buffer is needed.
The algorithm has three parts:
- Find the middle using the slow/fast pointer technique. slow moves one step at a time, fast moves two. When fast reaches the end, slow is at the midpoint.
- Split the list into two halves by cutting the link after slow.
- Recursively sort each half, then merge the two sorted halves using the standard two-pointer merge.
The base case is a list with zero or one node, which is already sorted.
Visual walkthrough
Let's trace through [4, 2, 1, 3] step by step. First you find the middle and split, then recurse down to single nodes, then merge back up.
Step 1: Find the middle. slow and fast both start at head.
Use the slow/fast pointer technique. slow moves 1 step, fast moves 2 steps.
Step 2: Advance pointers. slow = node 2, fast = node 1.
After one iteration: slow is at index 1, fast is at index 2.
Step 3: fast.next.next is null. Stop. slow is the midpoint.
Cut the link after slow. Left half: [4, 2]. Right half: [1, 3].
Step 4: Recurse on left half [4, 2]. Split into [4] and [2].
Single-node lists are already sorted. Now merge them.
Step 5: Merge [4] and [2]. Compare heads, pick smaller first.
2 is smaller than 4. Result: [2, 4]. Left half is now sorted.
Step 6: Recurse on right half [1, 3]. Split into [1] and [3].
Single-node lists again. Merge them.
Step 7: Merge [1] and [3]. 1 is smaller.
1 is smaller than 3. Result: [1, 3]. Right half is now sorted.
Step 8: Merge [2, 4] and [1, 3]. Compare 2 vs 1. Pick 1.
1 from right half is smaller. Result so far: [1].
Step 9: Compare 2 vs 3. Pick 2. Then compare 4 vs 3. Pick 3. Then append 4.
Final sorted list: [1, 2, 3, 4]. Done!
Notice how the recursion bottoms out at single-node lists (which are trivially sorted), and then the merge steps build up the sorted result from the bottom. Each merge uses the same two-pointer technique as Merge Two Sorted Lists.
The code
Here is the Python solution:
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def sort_list(head):
if not head or not head.next:
return head
slow, fast = head, head.next
while fast and fast.next:
slow = slow.next
fast = fast.next.next
mid = slow.next
slow.next = None
left = sort_list(head)
right = sort_list(mid)
return merge(left, right)
def merge(l1, l2):
dummy = ListNode(0)
curr = dummy
while l1 and l2:
if l1.val <= l2.val:
curr.next = l1
l1 = l1.next
else:
curr.next = l2
l2 = l2.next
curr = curr.next
curr.next = l1 if l1 else l2
return dummy.next
Key details to notice:
fast = head.nextinstead offast = head. This ensures that for a two-node list, slow stays at the first node so you split into[first]and[second]rather than getting stuck in infinite recursion.slow.next = Nonesevers the link between the two halves. Without this, the left half would still point into the right half.- The
mergefunction is identical to the solution for Merge Two Sorted Lists. It uses a dummy head node and two pointers, picking the smaller value at each step. l1.val <= l2.valuses<=to maintain stability (equal values from the left half come first).
The split point matters. Setting fast = head.next makes slow land on the last node of the left half for even-length lists. This avoids the edge case where a two-node list would never split.
Complexity analysis
| Metric | Value | Reasoning |
|---|---|---|
| Time | O(n log n) | Each level of recursion does O(n) work for splitting and merging. There are O(log n) levels since you halve the list each time. |
| Space | O(log n) | The recursion stack goes O(log n) levels deep. No extra data structures are used. |
The follow-up asks for O(1) space. The recursive version uses O(log n) space due to the call stack. A bottom-up iterative merge sort can achieve true O(1) space by merging sublists of increasing size (1, 2, 4, 8, ...) without recursion. The recursive version shown here is the one you will typically need in interviews.
The building blocks
This problem composes two reusable building blocks that appear in many linked list problems.
Slow/fast pointer to find the middle
You set up two pointers at the head. slow advances one node per step, fast advances two. When fast reaches the end (or its next is null), slow is at the middle. This is the same technique used in:
- Linked List Cycle (LeetCode 141) to detect cycles
- Reorder List (LeetCode 143) to find the midpoint before reversing the second half
- Palindrome Linked List (LeetCode 234) to split and compare halves
Merge two sorted lists
Given two sorted linked lists, merge them into one by comparing heads and picking the smaller value. This is the complete solution to Merge Two Sorted Lists (LeetCode 21), and it also forms the core of:
- Merge K Sorted Lists (LeetCode 23) using divide-and-conquer or a heap
- Any divide-and-conquer algorithm on linked lists
Once these two building blocks are automatic, the sort list solution is just the framework that connects them.
Edge cases to watch for
- Empty list.
headisNone. The base case returnsNoneimmediately. - Single node.
head.nextisNone. Already sorted. The base case handles this. - Two nodes.
[2, 1]. slow stays at node 0 (sincefast = head.nextandfast.nextisNone). Split into[2]and[1]. Merge produces[1, 2]. - Already sorted.
[1, 2, 3, 4]. The algorithm still splits and merges, but each merge step just walks through in order. No extra cost beyond the O(n log n) comparisons. - All identical values.
[3, 3, 3]. The<=in the merge function handles this cleanly. The list comes back unchanged.
From understanding to recall
Merge sort on a linked list is a pattern that combines two well-known building blocks. Once you see it, the logic feels natural. But reproducing it under pressure requires remembering specific details: where to initialize fast, where to cut the list, and the dummy-head merge pattern.
Reading the solution once is not enough. The gap between "I understand this" and "I can write this from scratch" is where practice matters. Spaced repetition drills lock in those details so that when you see a sorting or linked list problem, the pieces snap together automatically.
Related posts
- Merge Two Sorted Lists - The merge building block used in this solution, LeetCode 21
- Linked List Cycle - The slow/fast pointer technique for cycle detection, LeetCode 141
Visual Learner? Explore how linked list operations work with our Linked List Interactive Visualization.