We need to sort a singly linked list in ascending order. Unlike arrays, we cannot access elements by index, so algorithms like quicksort, which rely on random access for efficient partitioning, do not translate well. The sort has to work with sequential access only.
Merge sort fits linked lists well. On arrays, the merge step needs an O(n) temporary buffer to combine two sorted halves. On a linked list, the same merge step rearranges existing pointers, so it costs O(1) extra space. The one part that needs care is finding the middle of the list to split it, and a slow-fast pointer walk does that in O(n).
0 <= n <= 5 * 10^4 → With up to 50,000 nodes, an O(n^2) approach like insertion sort or bubble sort would be around 2.5 billion operations in the worst case. That's too slow. We need O(n log n).-10^5 <= Node.val <= 10^5 → Values fit comfortably in a 32-bit integer. No overflow concerns.Sidestep the linked list constraints entirely. Copy all values into an array, sort the array with a built-in sort, then write the sorted values back into the nodes in order. The node structure never changes, only the values stored in it.
This is correct and easy to write, but it uses O(n) extra space for the array, which the follow-up asks us to avoid.
Input:
After collecting values and sorting: [1, 2, 3, 4]. Write sorted values back to nodes:
This works but uses O(n) extra space. The next approach sorts the nodes in place by rearranging pointers, using merge sort's divide-and-conquer structure.
Apply merge sort's divide and conquer directly to the nodes: find the middle of the list with the slow-fast pointer technique, split the list into two halves, recursively sort each half, then merge the two sorted halves back together. The merge step links existing nodes instead of copying values, so it needs no temporary buffer.
Each recursion level splits every list in half, so the depth of the recursion is log(n) levels. At each level the merge steps together touch every node once, which is O(n) work per level. Multiplying the two gives O(n log n). The slow-fast walk that locates the midpoint is also O(n) per level, so it only adds a constant factor and does not change the bound.
The O(log n) space comes from the recursion stack. To reach the O(1) space the follow-up asks for, the next approach removes the recursion and merges from the bottom up, starting with individual nodes and doubling the merge size each round.
The top-down approach splits the list in half recursively, which requires O(log n) stack space for the call stack. The bottom-up approach reverses the order of work. Instead of splitting first and merging on the way back up, it starts by treating every node as a sorted sublist of size 1, merges adjacent pairs into sorted sublists of size 2, then merges adjacent size-2 sublists into size-4 sublists, and so on.
Each pass walks all n nodes, and there are log(n) passes because the merge size doubles each time (1, 2, 4, up to n). The work iterates instead of recursing, so the only extra memory is a fixed set of pointers, giving O(1) space.