Sort List
Top-down merge sort on a linked list — slow/fast split, recurse, merge two sorted lists.
4 min read
linked-list merge-sort divide-and-conquer
Problem#
Sort a singly linked list in ascending order.
Examples#
[4,2,1,3]→[1,2,3,4].[-1,5,3,4,0]→[-1,0,3,4,5].
Constraints#
0 <= nodes <= 5 * 10^4.
Approach#
Top-down merge sort. Find the middle with slow/fast pointers, split, sort each half recursively, merge.
Solution#
struct ListNode { int val; ListNode* next; };
class Solution { ListNode* merge(ListNode* a, ListNode* b) { ListNode dummy(0), *tail = &dummy; dummy.next = nullptr; while (a && b) { if (a->val <= b->val) { tail->next = a; a = a->next; } else { tail->next = b; b = b->next; } tail = tail->next; } tail->next = a ? a : b; return dummy.next; }public: ListNode* sortList(ListNode* head) { if (!head || !head->next) return head; ListNode *slow = head, *fast = head->next; while (fast && fast->next) { slow = slow->next; fast = fast->next->next; } ListNode* mid = slow->next; slow->next = nullptr; return merge(sortList(head), sortList(mid)); }};func sortList(head *ListNode) *ListNode { if head == nil || head.Next == nil { return head } slow, fast := head, head.Next for fast != nil && fast.Next != nil { slow = slow.Next fast = fast.Next.Next } mid := slow.Next slow.Next = nil return mergeSorted(sortList(head), sortList(mid))}
func mergeSorted(a, b *ListNode) *ListNode { dummy := &ListNode{} tail := dummy for a != nil && b != nil { if a.Val <= b.Val { tail.Next = a a = a.Next } else { tail.Next = b b = b.Next } tail = tail.Next } if a != nil { tail.Next = a } else { tail.Next = b } return dummy.Next}from typing import Optional
class Solution: def sortList(self, head: Optional[ListNode]) -> Optional[ListNode]: 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 return self._merge(self.sortList(head), self.sortList(mid))
def _merge(self, a: Optional[ListNode], b: Optional[ListNode]) -> Optional[ListNode]: dummy = ListNode(0) tail = dummy while a and b: if a.val <= b.val: tail.next = a a = a.next else: tail.next = b b = b.next tail = tail.next tail.next = a if a else b return dummy.nextfunction sortList(head) { if (!head || !head.next) return head; let slow = head, fast = head.next; while (fast && fast.next) { slow = slow.next; fast = fast.next.next; } const mid = slow.next; slow.next = null; return merge(sortList(head), sortList(mid));}
function merge(a, b) { const dummy = new ListNode(0); let tail = dummy; while (a && b) { if (a.val <= b.val) { tail.next = a; a = a.next; } else { tail.next = b; b = b.next; } tail = tail.next; } tail.next = a ? a : b; return dummy.next;}class Solution { public ListNode sortList(ListNode head) { if (head == null || head.next == null) return head; ListNode slow = head, fast = head.next; while (fast != null && fast.next != null) { slow = slow.next; fast = fast.next.next; } ListNode mid = slow.next; slow.next = null; return merge(sortList(head), sortList(mid)); }
private ListNode merge(ListNode a, ListNode b) { ListNode dummy = new ListNode(0); ListNode tail = dummy; while (a != null && b != null) { if (a.val <= b.val) { tail.next = a; a = a.next; } else { tail.next = b; b = b.next; } tail = tail.next; } tail.next = a != null ? a : b; return dummy.next; }}function sortList(head: ListNode | null): ListNode | null { if (!head || !head.next) return head; let slow: ListNode = head, fast: ListNode | null = head.next; while (fast && fast.next) { slow = slow.next!; fast = fast.next.next; } const mid = slow.next; slow.next = null; return merge(sortList(head), sortList(mid));}
function merge(a: ListNode | null, b: ListNode | null): ListNode | null { const dummy = new ListNode(0); let tail: ListNode = dummy; while (a && b) { if (a.val <= b.val) { tail.next = a; a = a.next; } else { tail.next = b; b = b.next; } tail = tail.next!; } tail.next = a ? a : b; return dummy.next;}Editorial#
Linked lists have O(1) splice and merge but no random access, making merge sort ideal — it’s stable, doesn’t rely on indexing, and matches list semantics. The fast = head->next start ensures the split lands at the correct midpoint for even-length lists.
Complexity#
- Time:
O(n log n). - Space:
O(log n)recursion stack.
Concept revision#
Related#