147. Insertion Sort List
Problem Description
You are given the head of a singly linked list. Your task is to sort this linked list using the insertion sort algorithm and return the head of the sorted list.
How Insertion Sort Works:
Insertion sort builds the final sorted list one element at a time. It works similarly to how you might sort playing cards in your hands:
- Start with an empty sorted portion (or consider the first element as already sorted)
- Take one element from the unsorted portion
- Find the correct position in the sorted portion where this element should be inserted
- Insert the element at that position
- Repeat until all elements are processed
For a Linked List:
- Initially, consider the first node as the sorted portion
- For each subsequent node:
- Remove it from its current position
- Traverse the sorted portion from the beginning to find where it belongs
- Insert it at the correct position to maintain sorted order
- Continue with the next unsorted node
Example:
If the input linked list is 4 -> 2 -> 1 -> 3
, the sorting process would be:
- Start:
4
(sorted) |2 -> 1 -> 3
(unsorted) - Step 1: Insert 2 →
2 -> 4
(sorted) |1 -> 3
(unsorted) - Step 2: Insert 1 →
1 -> 2 -> 4
(sorted) |3
(unsorted) - Step 3: Insert 3 →
1 -> 2 -> 3 -> 4
(sorted)
The algorithm maintains the linked list structure while rearranging the nodes to achieve sorted order based on their values in ascending order.
Intuition
When implementing insertion sort on a linked list, we need to think about how to efficiently manage node connections while sorting. Unlike arrays where we shift elements, in linked lists we manipulate pointers.
The key insight is to use a dummy node at the beginning of our sorted portion. This dummy node serves as an anchor point that simplifies our insertion logic - we never have to worry about updating the head of the list separately.
Here's the thought process:
-
Why check if
pre.val <= cur.val
? If the current node is already in the correct position (greater than or equal to the previous node), we don't need to move it. This optimization saves us from unnecessary traversal and pointer manipulation. -
Finding the insertion point: When we need to move a node, we start from the dummy node and traverse the sorted portion until we find where
cur
belongs. We're looking for the spot wherep.next.val > cur.val
- this meanscur
should be inserted betweenp
andp.next
. -
The pointer manipulation dance:
- Save
cur.next
in a temporary variablet
(we'll need this to continue iteration) - Insert
cur
into its new position by settingcur.next = p.next
andp.next = cur
- Fix the gap left by removing
cur
by settingpre.next = t
- Move to the next unsorted node by setting
cur = t
- Save
The beauty of this approach is that we're building our sorted list in-place. We're not creating new nodes - just rearranging the existing ones. The dummy node eliminates edge cases when inserting at the beginning, and by maintaining a pre
pointer, we can efficiently remove nodes from their current position without losing track of where we are in the traversal.
Solution Approach
Let's walk through the implementation step by step:
Initial Setup:
if head is None or head.next is None:
return head
First, we handle edge cases - if the list is empty or has only one node, it's already sorted.
dummy = ListNode(head.val, head) pre, cur = dummy, head
We create a dummy node that points to the head. This dummy acts as a sentinel before our sorted portion. We initialize two pointers:
pre
: tracks the last node in the sorted portioncur
: points to the current node being processed
Main Sorting Loop:
while cur:
We iterate through each node in the original list.
Optimization Check:
if pre.val <= cur.val:
pre, cur = cur, cur.next
continue
If the current node is already in the correct position (greater than or equal to the previous node), we simply advance both pointers. This is an important optimization that keeps already-sorted sequences intact.
Finding Insertion Position:
p = dummy
while p.next.val <= cur.val:
p = p.next
When we need to move a node, we start from the dummy node and traverse the sorted portion. We stop when p.next.val > cur.val
, meaning cur
should be inserted between p
and p.next
.
Pointer Manipulation:
t = cur.next # Save the next node to process
cur.next = p.next # Insert cur after p
p.next = cur # Complete the insertion
pre.next = t # Remove cur from its old position
cur = t # Move to the next unsorted node
This sequence carefully:
- Saves the reference to the next unsorted node (
t
) - Inserts
cur
into its correct position in the sorted portion - Patches the gap left by removing
cur
from its original position - Moves to process the next node
Return Result:
return dummy.next
Since our dummy node has the same value as the original head, dummy.next
points to the actual head of our sorted list.
Time Complexity: O(n²)
- In the worst case (reverse sorted list), for each of the n
nodes, we might traverse up to n
nodes to find the insertion position.
Space Complexity: O(1)
- We only use a constant amount of extra space for pointers and the dummy node.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's trace through sorting the linked list 3 -> 1 -> 4 -> 2
step by step:
Initial State:
- Create dummy node with value 3, pointing to head
dummy -> 3 -> 1 -> 4 -> 2
pre = dummy
,cur = 3
(first node)
Iteration 1: Process node 3
- Check: Is
pre.val (3) <= cur.val (3)
? Yes! - No movement needed, advance pointers
pre = 3
,cur = 1
- List unchanged:
3 -> 1 -> 4 -> 2
Iteration 2: Process node 1
- Check: Is
pre.val (3) <= cur.val (1)
? No, need to move1
- Find insertion position: Start at dummy, traverse while
p.next.val <= 1
p = dummy
, checkp.next.val (3) <= 1
? No, stop here- Insert
1
after dummy (before3
)
- Pointer manipulation:
t = 4
(save next node)1.next = 3
(insert 1 before 3)dummy.next = 1
(dummy points to 1)3.next = 4
(fix the gap)cur = 4
(move to next)
- List becomes:
1 -> 3 -> 4 -> 2
Iteration 3: Process node 4
- Check: Is
pre.val (3) <= cur.val (4)
? Yes! - No movement needed, advance pointers
pre = 4
,cur = 2
- List unchanged:
1 -> 3 -> 4 -> 2
Iteration 4: Process node 2
- Check: Is
pre.val (4) <= cur.val (2)
? No, need to move2
- Find insertion position: Start at dummy, traverse while
p.next.val <= 2
p = dummy
, checkp.next.val (1) <= 2
? Yes, continuep = 1
, checkp.next.val (3) <= 2
? No, stop here- Insert
2
between1
and3
- Pointer manipulation:
t = None
(no next node)2.next = 3
(insert 2 before 3)1.next = 2
(1 points to 2)4.next = None
(fix the gap)cur = None
(done)
- List becomes:
1 -> 2 -> 3 -> 4
Final Result: Return dummy.next
which points to 1
, giving us the sorted list 1 -> 2 -> 3 -> 4
Solution Implementation
1# Definition for singly-linked list.
2# class ListNode:
3# def __init__(self, val=0, next=None):
4# self.val = val
5# self.next = next
6
7class Solution:
8 def insertionSortList(self, head: ListNode) -> ListNode:
9 # Handle edge cases: empty list or single node
10 if head is None or head.next is None:
11 return head
12
13 # Create a dummy node to simplify insertion at the beginning
14 # Initialize with minimum possible value to avoid comparison issues
15 dummy = ListNode(float('-inf'))
16 dummy.next = head
17
18 # prev_node: last node in the sorted portion
19 # curr_node: current node being processed
20 prev_node = head
21 curr_node = head.next
22
23 while curr_node:
24 # If current node is already in correct position (greater than or equal to previous)
25 if prev_node.val <= curr_node.val:
26 # Move to next node
27 prev_node = curr_node
28 curr_node = curr_node.next
29 continue
30
31 # Find the correct position to insert curr_node in the sorted portion
32 insert_pos = dummy
33 while insert_pos.next.val < curr_node.val:
34 insert_pos = insert_pos.next
35
36 # Remove curr_node from its current position
37 next_to_process = curr_node.next
38 prev_node.next = next_to_process
39
40 # Insert curr_node at the found position
41 curr_node.next = insert_pos.next
42 insert_pos.next = curr_node
43
44 # Move to the next node to process
45 curr_node = next_to_process
46
47 # Return the head of the sorted list (skip dummy node)
48 return dummy.next
49
1/**
2 * Definition for singly-linked list.
3 * public class ListNode {
4 * int val;
5 * ListNode next;
6 * ListNode() {}
7 * ListNode(int val) { this.val = val; }
8 * ListNode(int val, ListNode next) { this.val = val; this.next = next; }
9 * }
10 */
11class Solution {
12 public ListNode insertionSortList(ListNode head) {
13 // Handle edge cases: empty list or single node
14 if (head == null || head.next == null) {
15 return head;
16 }
17
18 // Create a dummy node with value smaller than any possible value
19 // This serves as the head of our sorted portion
20 ListNode dummy = new ListNode(Integer.MIN_VALUE);
21 dummy.next = head;
22
23 // Previous node in the original list and current node to be inserted
24 ListNode previousNode = head;
25 ListNode currentNode = head.next;
26
27 // Process each node in the original list
28 while (currentNode != null) {
29 // If current node is already in correct position (greater than or equal to previous)
30 // Simply move forward
31 if (previousNode.val <= currentNode.val) {
32 previousNode = currentNode;
33 currentNode = currentNode.next;
34 continue;
35 }
36
37 // Find the correct insertion position in the sorted portion
38 ListNode insertPosition = dummy;
39 while (insertPosition.next.val < currentNode.val) {
40 insertPosition = insertPosition.next;
41 }
42
43 // Remove current node from its current position
44 ListNode nextNode = currentNode.next;
45 previousNode.next = nextNode;
46
47 // Insert current node at the found position
48 currentNode.next = insertPosition.next;
49 insertPosition.next = currentNode;
50
51 // Move to the next node to be processed
52 currentNode = nextNode;
53 }
54
55 // Return the head of the sorted list (skip dummy node)
56 return dummy.next;
57 }
58}
59
1/**
2 * Definition for singly-linked list.
3 */
4struct ListNode {
5 int val;
6 ListNode* next;
7 ListNode() : val(0), next(nullptr) {}
8 ListNode(int x) : val(x), next(nullptr) {}
9 ListNode(int x, ListNode* next) : val(x), next(next) {}
10};
11
12class Solution {
13public:
14 /**
15 * Sorts a linked list using insertion sort algorithm
16 * @param head - The head of the linked list to be sorted
17 * @return The head of the sorted linked list
18 */
19 ListNode* insertionSortList(ListNode* head) {
20 // Handle edge cases: empty list or single node
21 if (head == nullptr || head->next == nullptr) {
22 return head;
23 }
24
25 // Create a dummy node to simplify insertion at the beginning
26 // Initialize dummy with a minimal value and point to nullptr initially
27 ListNode* dummy = new ListNode(INT_MIN);
28
29 // Current node being processed from the original list
30 ListNode* current = head;
31
32 // Process each node in the original list
33 while (current != nullptr) {
34 // Store the next node to process before modifying pointers
35 ListNode* next_node = current->next;
36
37 // Find the correct position to insert current node in sorted portion
38 ListNode* insert_position = dummy;
39 while (insert_position->next != nullptr &&
40 insert_position->next->val < current->val) {
41 insert_position = insert_position->next;
42 }
43
44 // Insert current node at the found position
45 current->next = insert_position->next;
46 insert_position->next = current;
47
48 // Move to the next node in the original list
49 current = next_node;
50 }
51
52 // Store the head of sorted list and clean up dummy node
53 ListNode* sorted_head = dummy->next;
54 delete dummy;
55
56 // Return the sorted list
57 return sorted_head;
58 }
59};
60
1/**
2 * Definition for singly-linked list node
3 */
4interface ListNode {
5 val: number;
6 next: ListNode | null;
7}
8
9/**
10 * Sorts a linked list using insertion sort algorithm
11 * @param head - The head of the linked list to be sorted
12 * @returns The head of the sorted linked list
13 */
14function insertionSortList(head: ListNode | null): ListNode | null {
15 // Handle edge cases: empty list or single node
16 if (head === null || head.next === null) {
17 return head;
18 }
19
20 // Create a dummy node to simplify insertion at the beginning
21 // Initialize dummy with head's value and point to head
22 const dummy: ListNode = {
23 val: head.val,
24 next: head
25 };
26
27 // Previous node in the original list and current node being processed
28 let previous: ListNode = dummy;
29 let current: ListNode | null = head;
30
31 // Process each node in the original list
32 while (current !== null) {
33 // If current node is already in correct position (greater than or equal to previous)
34 if (previous.val <= current.val) {
35 // Move forward without any insertion
36 previous = current;
37 current = current.next;
38 continue;
39 }
40
41 // Find the correct position to insert current node
42 let insertPosition: ListNode = dummy;
43 while (insertPosition.next !== null && insertPosition.next.val <= current.val) {
44 insertPosition = insertPosition.next;
45 }
46
47 // Store the next node to process
48 const nextNode: ListNode | null = current.next;
49
50 // Insert current node at the found position
51 current.next = insertPosition.next;
52 insertPosition.next = current;
53
54 // Connect previous node to the next node (skip current)
55 previous.next = nextNode;
56
57 // Move to the next node
58 current = nextNode;
59 }
60
61 // Return the sorted list (skip dummy node)
62 return dummy.next;
63}
64
Time and Space Complexity
Time Complexity: O(n²)
The algorithm implements insertion sort on a linked list. In the worst case (when the list is sorted in reverse order), for each node we need to:
- Traverse from the dummy node to find the correct insertion position
- For the i-th node, we may need to traverse up to i nodes to find the insertion point
- This results in
1 + 2 + 3 + ... + n = n(n+1)/2
operations - Therefore, the time complexity is
O(n²)
In the best case (when the list is already sorted), the condition pre.val <= cur.val
is always true, so we simply traverse the list once, giving O(n)
time complexity. However, we consider the worst-case for Big-O notation.
Space Complexity: O(1)
The algorithm only uses a constant amount of extra space:
- A dummy node created at the beginning
- A few pointer variables (
pre
,cur
,p
,t
) - No additional data structures that scale with input size
- The sorting is done in-place by rearranging the existing nodes
Therefore, the space complexity is O(1)
(constant space).
Common Pitfalls
Pitfall 1: Incorrect Dummy Node Initialization
Problem: The original code uses ListNode(head.val, head)
to create the dummy node. This creates a duplicate node with the same value as the head, which becomes part of the final list and causes incorrect results.
Why it happens: The constructor ListNode(head.val, head)
creates a new node with head.val
as its value and head
as its next pointer. This means you're adding an extra node to your list rather than just creating a sentinel.
Correct approach:
# Wrong
dummy = ListNode(head.val, head)
# Correct
dummy = ListNode(float('-inf')) # or any value less than all possible node values
dummy.next = head
Pitfall 2: Not Handling the Boundary Between Sorted and Unsorted Portions
Problem: When moving a node from the unsorted portion to the sorted portion, failing to properly update the prev
pointer can lead to losing track of where the sorted portion ends.
Example scenario: If you insert a node into the sorted portion but don't maintain prev
correctly, you might process the same nodes multiple times or skip nodes.
Solution: After inserting a node, prev
should remain pointing to the last node that was naturally in order (not moved). Don't update prev
when you move a node backward.
Pitfall 3: Infinite Loop Due to Incorrect Pointer Updates
Problem: If the pointer manipulation sequence is wrong, you might create cycles in the linked list or lose references to parts of the list.
Common mistake:
# Wrong order - creates potential issues
cur.next = p.next
pre.next = cur.next # This now points to p.next, not the original cur.next!
p.next = cur
Correct sequence:
# Save next node first
next_node = cur.next
# Remove cur from current position
pre.next = next_node
# Insert cur in new position
cur.next = p.next
p.next = cur
# Update cur for next iteration
cur = next_node
Pitfall 4: Comparison Logic Error in Finding Insert Position
Problem: Using <=
instead of <
when finding the insertion position can cause duplicate values to be placed in the wrong order or create an infinite loop.
Why it matters: For stable sorting (maintaining relative order of equal elements), you should insert equal elements after existing equal elements, not before them.
# Potentially problematic for duplicates
while p.next.val <= cur.val: # might skip past equal values
p = p.next
# Better for stability
while p.next and p.next.val < cur.val: # stops at first equal or greater value
p = p.next
Pitfall 5: Forgetting to Check for Null Before Accessing Values
Problem: When traversing to find the insertion position, forgetting to check if p.next
exists before accessing p.next.val
causes null pointer exceptions.
Fix:
# Add null check
while p.next and p.next.val < cur.val:
p = p.next
These pitfalls are particularly tricky because they might work for some test cases but fail on edge cases like lists with duplicate values, already sorted lists, or single-element lists.
How many ways can you arrange the three letters A, B and C?
Recommended Readings
Linked List Cycle Given a linked list with potentially a loop determine whether the linked list from the first node contains a cycle in it For bonus points do this with constant space Parameters nodes The first node of a linked list with potentially a loop Result Whether there is a loop contained
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Don’t Miss This!