The Sort List problem is a common interview question that tests how well you understand the strengths and limitations of linked lists. Many candidates instinctively think about array-based sorting techniques, only to run into inefficiencies due to the lack of random access. This problem pushes you to think differently and choose an algorithm that works naturally with linked lists. It is especially useful for learning why merge sort is often the preferred choice in this setting.
Problem statement
You’re given the head of a singly linked list. Your task is to sort the linked list in ascending order and return the head of the sorted list.
Constraints:
- The number of nodes in the list is in the range [0, 5 * 104].
- -105 <= Node.val <= 105
Examples
| Input | Output | Explanation |
| 4 → 2 → 1 → 3 | 1 → 2 → 3 → 4 | The linked list is rearranged into ascending order. |
| -1 → 5 → 3 → 4 → 1 | -1 → 1 → 3 → 4 → 5 | All nodes are sorted while preserving the list structure. |
| 5 → 4 → 2 → 3 | 2 → 3 → 4 → 5 | The linked list is rearranged into ascending order. |
Why enumerating all possible orders doesn’t scale
One naive approach is to repeatedly scan the list to find the minimum element and rebuild the list one node at a time. This results in O(n^2) time complexity, which is not feasible when the list contains tens of thousands of nodes.
Optimized approach using bottom-up Merge Sort
Merge sort is a natural fit for linked lists because it relies on sequential access and pointer manipulation. A bottom-up merge sort performs the same logical steps as recursive merge sort but avoids recursion entirely.
Instead of dividing the list recursively, we iteratively merge sorted sublists of increasing size:
- First, merge sublists of size 1
- Then size 2
- Then size 3, and so on
This continues until the entire list is merged into a single sorted list.
Why bottom-up Merge Sort works well for Linked Lists
Linked lists allow efficient splitting and merging by reassigning pointers. Bottom-up merge sort takes advantage of this while also eliminating recursion, which saves stack space.
This makes the approach:
- Time-optimal (O(n log n))
- Space-efficient (O(1) extra space
- Safe for very large lists
Step-by-step algorithm
The following algorithmic steps outline how to efficiently sort a linked list in ascending order.
- If the list is empty or has only one node, return it.
- Compute the length
nof the linked list. - Create a dummy node that points to the head to simplify pointer handling.
- Set
step = 1to represent the current sublist size. - While
step < n:- Initialize
prevto the dummy node andcurrto the start of the list. - While
curris notNone:- Assign
left = curr. - Split the list after
stepnodes to getright. - Split again after
stepnodes to get the next starting point. - Merge
leftandright. - Connect the merged list to
prev.next. - Move
prevto the end of the merged list.
- Assign
- Double the sublist size:
step *= 2.
- Initialize
- Return
dummy.nextas the new head.
To better understand the algorithm, let’s walk through an illustration example below:
Python implementation
Let’s look at the code for the solution we just discussed.
Time complexity
The time complexity of this solution is O(n log n), where n is the number of nodes in the linked list. The list is processed in log n passes, and each pass merges all nodes once, resulting in overall logarithmic-linear performance.
Space complexity
The space complexity of this solution is O(1) extra space. The algorithm sorts the list in-place using pointer manipulation and avoids recursion, so no additional stack space is required.
Edge cases
- Empty list
- Example:
head = []→ output is[].
- Example:
- Single-node list
- Example:
head = [5]→ already sorted.
- Example:
- List with negative values
- Example:
head = [-3, -1, -2]→ correctly sorted to[-3, -2, -1].
- Example:
Common pitfalls
- Incorrect list splitting: Forgetting to break the list properly can cause infinite loops.
- Losing track of merged tails: Not returning the tail after merging leads to incorrect list connections.
- Using recursive merge sort unintentionally: This defeats the purpose of saving stack space.