You are given the head
of a singly linked list. Your task is to sort this list using the insertion sort algorithm and return the head of the sorted list.
Here's how insertion sort works:
Example 1:
head = [4, 2, 1, 3]
[1, 2, 3, 4]
Example 2:
head = [-1, 5, 3, 4, 0]
[-1, 0, 3, 4, 5]
Could you provide an efficient algorithm in Python to sort the linked list using insertion sort?
When you get asked this question in a real-life environment, it will often be ambiguous (especially at FAANG). Make sure to ask these questions in that case:
The brute force approach for sorting a linked list using insertion sort is similar to how you might sort a hand of playing cards. We'll build a new sorted list, one card (node) at a time, by finding the right spot for each card from the original unsorted list.
Here's how the algorithm would work step-by-step:
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def insertionSortList(head):
sorted_list_head = None
current_node = head
while current_node:
next_node = current_node.next
# Special case for inserting at the beginning
if not sorted_list_head or current_node.val < sorted_list_head.val:
current_node.next = sorted_list_head
sorted_list_head = current_node
else:
search_node = sorted_list_head
# Find the correct position to insert the current node
while search_node.next and current_node.val > search_node.next.val:
search_node = search_node.next
# Insert the current node in the sorted list
current_node.next = search_node.next
search_node.next = current_node
current_node = next_node
return sorted_list_head
We will construct the sorted list one element at a time, similar to how you might sort cards in your hand. We go through the original list, and for each element, we find the correct place to insert it into the already-sorted portion of the new list.
Here's how the algorithm would work step-by-step:
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def insertionSortList(head):
if not head:
return None
sorted_list_head = None
current_node = head
while current_node:
next_node = current_node.next
# Insert current_node into the sorted list
if not sorted_list_head:
sorted_list_head = current_node
sorted_list_head.next = None
else:
# Find the correct position to insert.
insertion_point_predecessor = None
insertion_point = sorted_list_head
while insertion_point and current_node.val > insertion_point.val:
insertion_point_predecessor = insertion_point
insertion_point = insertion_point.next
# Insert at the beginning of the list
if not insertion_point_predecessor:
current_node.next = sorted_list_head
sorted_list_head = current_node
else:
# Insert in the middle or end
current_node.next = insertion_point
insertion_point_predecessor.next = current_node
current_node = next_node
# Return the head of the sorted list.
return sorted_list_head
Case | How to Handle |
---|---|
Empty list | Return the head (null) immediately, as there's nothing to sort. |
List with only one element | Return the head directly, as a single-element list is already sorted. |
List already sorted | The algorithm should still function correctly, though it won't perform any actual swaps, resulting in O(n) complexity in this best-case scenario. |
List sorted in reverse order | This represents the worst-case scenario for insertion sort, leading to O(n^2) complexity as each element needs to be moved to the beginning. |
List with duplicate values | The algorithm should correctly handle duplicate values by inserting them in their appropriate sorted positions relative to the existing duplicates. |
List with negative numbers | The algorithm should correctly handle negative numbers by inserting them in their appropriate sorted positions. |
List with very large or very small numbers (close to integer limits) | The algorithm relies on comparisons, so ensure that the comparison operations do not cause integer overflow, potentially by using appropriate data types. |
Memory constraints with extremely large lists | Insertion sort is in-place, so memory usage is relatively low, but extremely large lists could still strain memory, potentially requiring consideration of external sorting algorithms in real-world scenarios. |