Sort List
To solve this coding challenge, we need to sort a singly-linked list in ascending order. The challenge is to follow an optimal algorithm that is efficient both in time and space complexity. A good approach involves using the Merge Sort algorithm, which benefits from a time complexity of \(O(n \log n)\) and can be adapted to work in-place, thus addressing the space constraint.
Merge Sort is particularly suited to linked lists because of their natural structure, which facilitates easy splitting and merging without requiring extra space (apart from the recursive stack in the typical implementation).
Explanation
The Merge Sort algorithm works by repeatedly splitting the list into two halves until each sublist consists of a single node. It then merges these sublists in a sorted manner. Here is the breakdown of the approach:- Base Case : If the input list is empty or contains only one node, return the list as it is already sorted.
- Splitting the List : Split the list into two halves using the slow-and-fast pointer technique. The slow pointer moves one step at a time while the fast pointer moves two steps at a time. When the fast pointer reaches the end, the slow pointer will be at the middle point.
- Recursive Sorting : Recursively sort both halves of the list.
- Merge : Merge the two sorted halves back together to form a single sorted list. The overall sorting process ensures the list is sorted in \(O(n \log n)\) time. The recursion stack space used is \(O(\log n)\) but since the problem specifies \(O(1)\) additional memory, our implementation carefully avoids extra space usage besides the recursion stack.
- Base Case Check :
-
If the list is empty (
head
null
head.next
null
- Implement the Splitting Function :
- Use two pointers (__slow__ and __fast__) to find the middle of the list.
- Move __slow__ one step at a time and __fast__ two steps at a time.
- When __fast__ reaches the end, __slow__ will be at the midpoint.
- Split the list into two halves.
- Recursive Sort Function :
- Recursively apply sort to both halves of the list.
- Merge Sorted Lists :
- Implement a merge function that takes two sorted lists and merges them into one sorted list:
- Use a dummy node to simplify the merging process.
- Compare nodes from both lists, linking the smaller value node to the merged list, and move to the next node in that list.
- Once one list is exhausted, link the remaining part of the other list.
- Base Case Check :
-
if head is null or head.next is null: return head
-
This line checks if the
head
null
head.next
null
head
- Split List :
-
left_half, right_half = splitList(head)
-
Here the list is split into two parts using the
splitList
- Recursive Call :
-
left_sorted = sortList(left_half)
right_sorted = sortList(right_half)
-
sortList
- Merge Sorted Lists :
-
return mergeLists(left_sorted, right_sorted)
-
The
mergeLists
Step-by-Step Explanation:
# Function to sort linked list using merge sort
function sortList(head):
# Base case: if list is empty or has only one element
if head is null or head.next is null:
return head
# Split the list into two halves
left_half, right_half = splitList(head)
# Recursively sort both halves
left_sorted = sortList(left_half)
right_sorted = sortList(right_half)
# Merge the sorted halves and return the result
return mergeLists(left_sorted, right_sorted)
# Function to split the linked list into two halves
function splitList(head):
# Initialize two pointers, slow and fast
slow = head
fast = head
# Move slow pointer one step and fast pointer two steps until fast reaches the end
while fast.next is not null and fast.next.next is not null:
slow = slow.next
fast = fast.next.next
# Split the list
mid = slow.next
slow.next = null
return head, mid
# Function to merge two sorted linked lists
function mergeLists(l1, l2):
# Create a dummy node to act as the start of the merged list
dummy = new ListNode()
# Initialize current pointer to dummy
current = dummy
# While both lists are non-empty
while l1 is not null and l2 is not null:
# Compare the values of the two head nodes
if l1.val < l2.val:
# Link to the smaller value node
current.next = l1
l1 = l1.next
else:
current.next = l2
l2 = l2.next
# Move the current pointer
current = current.next
# If any nodes remain in either list, link them directly
if l1 is not null:
current.next = l1
else:
current.next = l2
# Return the merged list, skipping the dummy node
return dummy.next