147. Insertion Sort List - Explanation
Description
You are given the head of a singly linked list, sort the list using insertion sort, and return the sorted list's head.
The steps of the insertion sort algorithm:
- Insertion sort iterates, consuming one input element each repetition and growing a sorted output list.
- At each iteration, insertion sort removes one element from the input data, finds the location it belongs within the sorted list and inserts it there.
- It repeats until no input elements remain.
Example 1:
Input: head = [4,2,1,3]
Output: [1,2,3,4]Example 2:
Input: head = [-1,5,3,4,0]
Output: [-1,0,3,4,5]Constraints:
1 <= The length of the list <= 5000.-5000 <= Node.val <= 5000
Topics
Prerequisites
Before attempting this problem, you should be comfortable with:
- Linked Lists - Node traversal, insertion, and pointer manipulation
- Insertion Sort Algorithm - Understanding how elements are shifted to maintain sorted order
- Dummy Node Technique - Using a sentinel node to simplify edge cases at the head of a list
1. Convert To Array
Intuition
Since sorting a linked list in place can be tricky, we can simplify the problem by extracting all the values into an array. Once in array form, we can use any standard sorting algorithm. After sorting, we traverse the linked list again and overwrite each node's value with the sorted values in order.
Algorithm
- Traverse the linked list and collect all node values into an array.
- Sort the array.
- Traverse the linked list again, replacing each node's value with the next sorted value from the array.
- Return the head of the list.
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def insertionSortList(self, head: Optional[ListNode]) -> Optional[ListNode]:
arr = []
cur = head
while cur:
arr.append(cur.val)
cur = cur.next
arr.sort()
cur = head
for val in arr:
cur.val = val
cur = cur.next
return headTime & Space Complexity
- Time complexity:
- Space complexity:
2. Swapping Values
Intuition
This approach mimics insertion sort by comparing values rather than rearranging node pointers. For each node, we scan from the head to find any earlier node with a larger value and swap values. This bubbles smaller values toward the front, eventually producing a sorted list. While simpler to implement than pointer manipulation, it still requires O(n^2) comparisons.
Algorithm
- Start with the second node (
cur = head.next). - For each
cur, traverse fromheadtocur:- If any node
tmphas a value greater thancur.val, swap their values.
- If any node
- Move
curto the next node and repeat. - Return the
headof the list.
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def insertionSortList(self, head: Optional[ListNode]) -> Optional[ListNode]:
cur = head.next
while cur:
tmp = head
while tmp != cur:
if tmp.val > cur.val:
tmp.val, cur.val = cur.val, tmp.val
tmp = tmp.next
cur = cur.next
return headTime & Space Complexity
- Time complexity:
- Space complexity: extra space.
3. Swapping Nodes
Intuition
This is the classic insertion sort adapted for linked lists. Instead of swapping values, we physically remove a node and reinsert it at the correct position in the already sorted portion. A dummy node simplifies insertions at the head. If the current node is already in order relative to the previous node, we just advance. Otherwise, we unlink it and search from the beginning for the right spot.
Algorithm
- Create a
dummynode pointing to theheadto handle edge cases. - Maintain
prevas the last node of the sorted portion andcuras the node being examined. - If
cur.val >= prev.val, the node is in place; advance both pointers. - Otherwise, find the correct insertion point by scanning from the
dummy:- Unlink
curfrom its current position. - Insert
curafter the found position. - Update
curtoprev.nextto continue.
- Unlink
- Return
dummy.next.
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def insertionSortList(self, head: Optional[ListNode]) -> Optional[ListNode]:
dummy = ListNode(0, head)
prev, cur = head, head.next
while cur:
if cur.val >= prev.val:
prev, cur = cur, cur.next
continue
tmp = dummy
while cur.val > tmp.next.val:
tmp = tmp.next
prev.next = cur.next
cur.next = tmp.next
tmp.next = cur
cur = prev.next
return dummy.nextTime & Space Complexity
- Time complexity:
- Space complexity: extra space.
Common Pitfalls
Losing Track of the Next Node Before Unlinking
When removing a node from its current position to reinsert it elsewhere, failing to save cur.next before modifying pointers causes the reference to the next node to be lost. Always store the next node in a temporary variable before performing any pointer manipulation.
Not Using a Dummy Node for Head Insertions
When the smallest element is found later in the list and needs to become the new head, not using a dummy node makes head updates awkward and error-prone. A dummy node pointing to the head simplifies insertions at the beginning by providing a consistent previous node.
Incorrectly Advancing Pointers After Insertion
After inserting a node into its correct sorted position, the prev pointer should not advance because the node that was after cur is now directly after prev. Advancing prev would skip a node. Only cur should be updated to prev.next to continue with the next unsorted element.
Sign in to join the discussion