Given the head of a singly linked list head
, sort the list using insertion sort and return the sorted list's head.
To solve this problem, we can draw inspiration from the classic insertion sort algorithm, which is often used for sorting arrays. The key idea is to build a sorted portion of the list one node at a time by inserting each node into its correct position. However, unlike arrays, linked lists do not allow random access to elements, so we have to traverse the list to find the correct insertion point for each node.
A brute-force approach would be to repeatedly search for the smallest node and move it to the front, but this would be inefficient. Instead, by mimicking how insertion sort works, we can efficiently insert each node into its proper place in the sorted portion of the list as we traverse it.
The challenge is to manipulate the pointers correctly, ensuring that we do not lose track of any nodes or create cycles in the list.
Here’s a step-by-step approach to implementing insertion sort on a singly linked list:
current
) to traverse the list. For each node, determine if it is already in the correct position (i.e., its value is greater than or equal to the previous node's value). If so, move forward.
prev
) starting from the dummy node to find where in the sorted portion the current node should go.
prev
.
This approach ensures that at each step, the portion of the list before current
is always sorted.
Let's walk through an example with the input list: 4 -> 2 -> 1 -> 3
dummy -> 4
dummy -> 2 -> 4
dummy -> 1 -> 2 -> 4
dummy -> 1 -> 2 -> 3 -> 4
Final sorted list: 1 -> 2 -> 3 -> 4
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
class Solution:
def insertionSortList(self, head: ListNode) -> ListNode:
dummy = ListNode(0)
curr = head
while curr:
prev = dummy
# Find the right place to insert curr
while prev.next and prev.next.val < curr.val:
prev = prev.next
next_temp = curr.next
# Insert curr between prev and prev.next
curr.next = prev.next
prev.next = curr
curr = next_temp
return dummy.next
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(nullptr) {}
* };
*/
class Solution {
public:
ListNode* insertionSortList(ListNode* head) {
ListNode dummy(0);
ListNode* curr = head;
while (curr) {
ListNode* prev = &dummy;
while (prev->next && prev->next->val < curr->val) {
prev = prev->next;
}
ListNode* next_temp = curr->next;
curr->next = prev->next;
prev->next = curr;
curr = next_temp;
}
return dummy.next;
}
};
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode insertionSortList(ListNode head) {
ListNode dummy = new ListNode(0);
ListNode curr = head;
while (curr != null) {
ListNode prev = dummy;
while (prev.next != null && prev.next.val < curr.val) {
prev = prev.next;
}
ListNode nextTemp = curr.next;
curr.next = prev.next;
prev.next = curr;
curr = nextTemp;
}
return dummy.next;
}
}
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} head
* @return {ListNode}
*/
var insertionSortList = function(head) {
let dummy = new ListNode(0);
let curr = head;
while (curr !== null) {
let prev = dummy;
while (prev.next !== null && prev.next.val < curr.val) {
prev = prev.next;
}
let nextTemp = curr.next;
curr.next = prev.next;
prev.next = curr;
curr = nextTemp;
}
return dummy.next;
};
O(n^2)
because for each node, you may traverse the entire list.
O(n^2)
in the worst case (e.g., a reversed list), as each insertion may require traversing the sorted portion of the list. However, if the list is already partially sorted, performance improves.
O(1)
additional space because sorting is done in place, and only a few pointers are used.
The main benefit of the optimized approach is that it avoids unnecessary node copying and always maintains a valid, sorted portion of the list.
In summary, insertion sort is well-suited for sorting linked lists because it can efficiently insert nodes without shifting elements, as would be required in an array. By carefully managing pointers and using a dummy node, we can sort the list in place with minimal extra memory. The solution is elegant because it leverages the properties of linked lists and the simplicity of insertion sort, making it easy to implement and understand.