#148 Sort List
frequently reported atByteDance / TikTokAmazonMicrosoftMeta
the problem
In plain words
Sort a singly linked list in ascending order. Follow-up: O(n log n) time and O(1) extra memory.
in head = [4,2,1,3]
out [1,2,3,4]
in head = [-1,5,3,4,0]
out [-1,0,3,4,5]
in head = []
out []
- 0 ≤ n ≤ 5·10⁴
- -10⁵ ≤ Node.val ≤ 10⁵
your turn
Try it before you peek
Read the examples, say the brute force out loud, then write code: here or on LeetCode ↗. Stuck for 20 minutes? Open one hint at a time.
Solve it in the browser
LeetCode-style: complete the class. Compiled by the real javac (Java 17) and judged right here — nothing is sent anywhere.
Solutions are hidden so you can try first. Even 20 minutes of real effort makes the answer stick far better than reading it.
Marking the problem solved, or opening every hint, also shows it.
the aha
Key insight
patternMerge sort & merging sorted runsFast & slow pointers
Lists have no random access, which kills quicksort's and heap sort's advantages — but merging two sorted lists is O(1) space by relinking. Merge sort is the natural fit.
solutions
From brute force to optimal
Let the library do the work, then overwrite node values.
- Collect values.
- Collections.sort.
- Rewrite node values in order.
The library sort.
The copied values.
class Solution { public ListNode sortList(ListNode head) { List<Integer> values = new ArrayList<>(); for (ListNode p = head; p != null; p = p.next) values.add(p.val); Collections.sort(values); int i = 0; for (ListNode p = head; p != null; p = p.next) p.val = values.get(i++); // rewrite in place return head; }}Find the middle with slow/fast pointers, cut, sort both halves, merge.
- Base: 0 or 1 node.
- slow/fast to find the middle; cut after slow.
- Recurse on both halves.
- Merge with a dummy head.
log n levels, O(n) splitting + merging per level.
Recursion stack only — no arrays.
class Solution { public ListNode sortList(ListNode head) { if (head == null || head.next == null) return head; // Split in the middle with slow/fast pointers. ListNode slow = head, fast = head.next; while (fast != null && fast.next != null) { slow = slow.next; fast = fast.next.next; } ListNode right = slow.next; slow.next = null; // cut the list in two return merge(sortList(head), sortList(right)); } private ListNode merge(ListNode a, ListNode b) { ListNode dummy = new ListNode(0), tail = dummy; while (a != null && b != null) { if (b.val < a.val) { tail.next = b; b = b.next; } else { tail.next = a; a = a.next; } // ties take `a` → stable tail = tail.next; } tail.next = (a != null) ? a : b; return dummy.next; }}Merge runs of size 1, 2, 4, … iteratively — no recursion at all.
- Count n.
- For width = 1, 2, 4… < n: cut pairs of runs and merge them onto the tail.
- Stop when one run covers the list.
log n widths × one O(n) pass each.
Only a few pointers; merging just relinks nodes.
class Solution { public ListNode sortList(ListNode head) { int n = 0; for (ListNode p = head; p != null; p = p.next) n++; ListNode dummy = new ListNode(0, head); for (int width = 1; width < n; width *= 2) { // merge runs of 1, 2, 4, ... ListNode prev = dummy, cur = dummy.next; while (cur != null) { ListNode left = cur; ListNode right = cut(left, width); // left has `width` nodes cur = cut(right, width); // right has up to `width` nodes prev = mergeAndAttach(prev, left, right); // returns the new tail } } return dummy.next; } /** Detaches the first `k` nodes starting at `node`; returns the head of what follows. */ private ListNode cut(ListNode node, int k) { for (int i = 1; node != null && i < k; i++) node = node.next; if (node == null) return null; ListNode rest = node.next; node.next = null; return rest; } private ListNode mergeAndAttach(ListNode prev, ListNode a, ListNode b) { while (a != null && b != null) { if (b.val < a.val) { prev.next = b; b = b.next; } // strictly smaller: take from b else { prev.next = a; a = a.next; } // ties take a → stable prev = prev.next; } prev.next = (a != null) ? a : b; // one run is used up: attach the other while (prev.next != null) prev = prev.next; // walk to the new tail return prev; }}Dry run: merge runs of 1, 2, 4…
in the room
How to say it in the interview
talk track
- Say why merge sort: no random access needed, merge is O(1) space on lists.
- Code top-down first (clearer). Offer bottom-up if they push for O(1) space.
traps
- Starting fast at head (not head.next) — with 2 nodes the split never shrinks → infinite recursion.
- Forgetting
slow.next = null— the halves stay connected.
what they ask next
Follow-ups
Merge k sorted lists (LC 23)?
Min-heap of list heads, O(N log k) — or merge pairs bottom-up, same bound.
before you submit
Edge cases to test
- Empty list → return null.
- Two nodes: start fast at head.next, or the split never shrinks and the recursion never ends.
- Duplicates: take from the left list on ties so equal values keep their order.
- 5·10⁴ nodes: a recursive merge function goes one call per node deep — prefer the iterative merge with a dummy node.
practise next
Similar problems
- #21Merge Two Sorted ListsEasyin Linked Lists
- #23Merge k Sorted ListsHardin Heaps
- #876Middle of the Linked ListEasyin Linked Lists