medium Sort List
Problem Statement
Given a head of the linked list, return the list after sorting it in ascending order.
Examples
-
Example 1:
- Input:
[3, 1, 2] - Expected Output:
[1, 2, 3] - Justification: The list is sorted in ascending order, with
1coming before2, and2before3.
- Input:
-
Example 2:
- Input:
[4] - Expected Output:
[4] - Justification: A list with a single element is already sorted.
- Input:
-
Example 3:
- Input:
[9, 8, 7, 6, 5, 4, 3, 2, 1] - Expected Output:
[1, 2, 3, 4, 5, 6, 7, 8, 9] - Justification: The list is sorted in ascending order, with each element being smaller than the next.
- Input:
Constraints:
- The number of nodes in the list is in the range [0, 5 * 104].
- -105 <= Node.val <= 105
Try it yourself
Try solving this question here:
✅ Solution Sort List
Problem Statement
Given a head of the linked list, return the list after sorting it in ascending order.
Examples
-
Example 1:
- Input:
[3, 1, 2] - Expected Output:
[1, 2, 3] - Justification: The list is sorted in ascending order, with
1coming before2, and2before3.
- Input:
-
Example 2:
- Input:
[4] - Expected Output:
[4] - Justification: A list with a single element is already sorted.
- Input:
-
Example 3:
- Input:
[9, 8, 7, 6, 5, 4, 3, 2, 1] - Expected Output:
[1, 2, 3, 4, 5, 6, 7, 8, 9] - Justification: The list is sorted in ascending order, with each element being smaller than the next.
- Input:
Constraints:
- The number of nodes in the list is in the range [0, 5 * 104].
- -105 <= Node.val <= 105
Solution
To sort a linked list, a merge sort algorithm can be used, which is renowned for its efficiency with linked lists due to its recursive nature and the ability to sort lists without additional space requirements for arrays. The approach involves dividing the list into smaller sublists until each sublist contains a single element or is empty. This division is based on the principle of recursion, where the list is split into halves repeatedly.
Once we have these small sublists, the next step is to merge them back together in a sorted manner. During the merging process, we compare the elements of the sublists and arrange them in ascending order. This merging is done until we reconstruct the entire list, but now in a sorted order.
Step-by-step algorithm
-
Divide the List into Two Halves:
- Step 1.1: Start with a method that takes the head of the list as its parameter.
- Step 1.2: If the list is empty or has only one node, it is already sorted, so return it as is.
- Step 1.3: Use two pointers, slow and fast, to find the middle of the list. The slow pointer advances one node at a time, while the fast pointer advances two nodes at a time. When the fast pointer reaches the end, the slow pointer will be at the midpoint.
- Step 1.4: Break the list into two parts at the midpoint. The first part starts from the head to the slow pointer, and the second part starts from the next node to the slow pointer to the end of the list.
-
Recursively Sort Each Half:
- Step 2.1: Recursively apply the same algorithm to the first half of the list.
- Step 2.2: Do the same for the second half of the list.
- Step 2.3: The recursion base case is a list with a single node or an empty list, which is considered sorted.
-
Merge the Two Sorted Halves:
- Step 3.1: After the two halves are sorted, merge them into a single sorted list.
- Step 3.2: Create a dummy node to help with merging.
- Step 3.3: Compare the heads of the two halves. Whichever node has the smaller value becomes the next node of the merged list.
- Step 3.4: Continue this process until all nodes from both halves are merged.
- Step 3.5: If one half is exhausted before the other, append the remaining nodes of the other half to the merged list.
-
Return the Sorted List:
- Step 4.1: The head of the merged list (starting from the next node of the dummy node) is the head of the sorted list.
- Step 4.2: Return the sorted list.
Algorithm Walkthrough
Given the input list [9, 8, 7, 6, 5, 4, 3, 2, 1]:
-
Initial Division:
- Divide the list into two halves:
[9, 8, 7, 6, 5]and[4, 3, 2, 1].
- Divide the list into two halves:
-
Further Division of First Half:
- Divide
[9, 8, 7, 6, 5]into[9, 8, 7]and[6, 5]. - Continue dividing:
[9, 8, 7]becomes[9, 8]and[7].[6, 5]becomes[6]and[5].
- Keep dividing until single elements:
[9, 8]becomes[9]and[8].
- Divide
-
Further Division of Second Half:
- Similarly, divide
[4, 3, 2, 1]into[4, 3]and[2, 1]. - Continue dividing:
[4, 3]becomes[4]and[3].[2, 1]becomes[2]and[1].
- Similarly, divide
-
Merging Sublists:
- Start merging:
- Merge
[9]and[8]into[8, 9]. - Merge
[8, 9]and[7]into[7, 8, 9]. - Merge
[6]and[5]into[5, 6]. - Merge
[7, 8, 9]and[5, 6]into[5, 6, 7, 8, 9].
- Merge
- For the second half:
- Merge
[4]and[3]into[3, 4]. - Merge
[2]and[1]into[1, 2]. - Merge
[3, 4]and[1, 2]into[1, 2, 3, 4].
- Merge
- Start merging:
-
Final Merge:
- Merge the two halves
[5, 6, 7, 8, 9]and[1, 2, 3, 4]into[1, 2, 3, 4, 5, 6, 7, 8, 9].
- Merge the two halves
Each step involves either dividing the list into smaller sublists or merging already sorted sublists, ensuring that the final list is sorted in ascending order.
Code
Here is the code for this algorithm:
// Definition for singly-linked list.
// private static class ListNode {
// int val;
// ListNode next;
// ListNode(int x) { val = x; }
// }
public class Solution {
// Method to sort the list
public ListNode sortList(ListNode head) {
// Base case: a single node or empty list is already sorted
if (head == null || head.next == null) {
return head;
}
// Find the middle of the list to divide it into two halves
ListNode slow = head,
fast = head,
prev = null;
while (fast != null && fast.next != null) {
prev = slow;
slow = slow.next;
fast = fast.next.next;
}
// Disconnect the first half from the second half
prev.next = null;
// Recursively sort each half
ListNode l1 = sortList(head);
ListNode l2 = sortList(slow);
// Merge the two sorted halves and return
return merge(l1, l2);
}
// Merge two sorted linked lists
private ListNode merge(ListNode l1, ListNode l2) {
ListNode dummy = new ListNode(0),
tail = dummy;
while (l1 != null && l2 != null) {
// Compare values from each list and append the smaller one
if (l1.val < l2.val) {
tail.next = l1;
l1 = l1.next;
} else {
tail.next = l2;
l2 = l2.next;
}
tail = tail.next;
}
// Append any remaining nodes from either list
tail.next = (l1 != null) ? l1 : l2;
return dummy.next; // Return the head of the merged list
}
public static void main(String[] args) {
Solution solution = new Solution();
// Constructing the linked list for Example 3
int[] values = { 9, 8, 7, 6, 5, 4, 3, 2, 1 };
ListNode head = new ListNode(values[0]);
ListNode current = head;
for (int i = 1; i < values.length; i++) {
current.next = new ListNode(values[i]);
current = current.next;
}
// Apply the sorting algorithm
head = solution.sortList(head);
// Display the sorted list
System.out.println("Sorted List:");
printList(head);
}
// Helper method to print the list
private static void printList(ListNode head) {
while (head != null) {
System.out.print(head.val + " ");
head = head.next;
}
System.out.println();
}
}
Time and Space Complexity
-
Time Complexity: ( O(n log n) )
- The algorithm recursively splits the list into halves (logarithmic number of splits) and then merges them (linear time for each merge), resulting in ( O(n \log n) ) time complexity.
-
Space Complexity: ( O(log n) )
- The space complexity is dominated by the recursive call stack, which grows proportionally to the height of the recursion tree, resulting in ( O(\log n) ) space complexity.
🤖 Don't fully get this? Learn it with Claude
Stuck on Sort List? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.
Progressively stronger hints — you still solve it.
I'm working on the problem **Sort List** (DSA). Give me a HINT LADDER: start with the tiniest nudge, then wait. Only reveal the next, stronger hint when I ask. Do NOT show the full solution unless I type 'show solution'. Keep me doing the thinking. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
See the technique, not just code.
Explain the optimal approach to **Sort List** with a VISUAL walkthrough: trace it on a small concrete example using ASCII art / a step-by-step diagram, narrate what changes each step, then give time & space complexity with a one-line derivation. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
Catch bugs, edge cases, sub-optimality.
I'll paste my solution to **Sort List**. Review it for correctness, missed edge cases, and time/space complexity, then coach me toward the optimal — don't just rewrite it. Ask me to paste my code now. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
Lock in recognition with look-alikes.
Give me 2 problems that use the SAME underlying pattern as **Sort List**. For each, let me attempt first, then review my answer and name the trigger signal that reveals the pattern. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.