Given the head
of a linked list, return the list after sorting it in ascending order.
Follow up: Can you sort the linked list in O(n logn)
time and O(1)
memory (i.e. constant space)?
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]
Example 3:
Input: head = [] Output: []
Constraints:
[0, 5 * 10^4]
.-10^5 <= Node.val <= 10^5
The description was taken from https://leetcode.com/problems/sort-list/.
#O(N(Log N)) Time, O(Log N) Space
class Solution:
def sortList(self, head: ListNode) -> ListNode:
if not head or not head.next:
return head
mid = self.get_mid(head)
left = self.sortList(head)
right = self.sortList(mid)
return self.merge(left,right)
def get_mid(self, head: ListNode) -> ListNode:
slow=fast=head
while fast.next and fast.next.next:
slow = slow.next
fast = fast.next.next
mid = slow.next
slow.next = None
return mid
def merge(self, left: ListNode, right:ListNode) -> ListNode:
dummy = ListNode(0)
merger = dummy
while left and right:
if left.val < right.val:
merger.next = left
left = left.next
else:
merger.next = right
right = right.next
merger = merger.next
merger.next = left or right
return dummy.next
If we're given a linked list and asked to sort it like in this problem and Merge K Sorted Lists, a Merge Sort is going to be a good option because we can recursively break down the list(s) into lists of single nodes, then sort and merge each of these nodes individually.
This is going to come down to a couple of steps:
Break the list into single nodes by recursively getting the middle node of the given partition and breaking the list from that point.
Once broken, sort and merge the two halves of the just broken partition within a Depth-First Search manner where the sorted partitions grow as the call stack becomes empty.
Since we are having to implement this algorithm on a linked list instead of an array, it makes it slightly more difficult.
To start, we will create a couple of helper functions.
The first will be get_mid
that will tell us which node of our current list is the middle node so that we can cut the list in half at this point.
def get_mid(self, head: ListNode) -> ListNode:
Since we don't have an array, we can't just return the middle node in constant time.
To get the middle node in a single pass we will use Floyd's Tortoise and Hare algorithm.
This works by having two pointers, fast
and slow
, initially at the head of the linked list.
slow=fast=head
We will use a loop to traverse the current list.
The loop will run until the fast pointer is either at or behind the tail of the list.
while fast.next and fast.next.next:
The reason why we want to stop if the fast pointer is two nodes away from a None value is that we are incrementing the pointer by two nodes each time.
While in the loop, we will increment the slow pointer by one node and the fast pointer by two.
while fast.next and fast.next.next:
slow = slow.next
fast = fast.next.next
Once we are out of the loop, that means the fast pointer is at or behind the tail, so the mid node would be the node one place after the slow pointer.
1->2->3->4->5->None
If the function had the input of [1,2,3,4,5] shown above, the fast pointer would break when it was two places away from the None value, one node from the tail. The slow pointer would be one node behind the mid node, three.
mid = slow.next
Once we know where the mid node is, we will break the current list into two lists.
The mid node will be the head of the second list.
1->2->None 3->4->5->None
slow.next = None
All there is left to do now is to return the mid node, three, which is the head of the right list.
return mid
We will also need a merge
function. This is the same function used in the solution of Merge Two Sorted Lists.
This function will take a left
and right
sublist, initially a single node since that is the size we broke the problem's initial list into, and it will use a pointer to sort the nodes in ascending order before returning a sorted sublist. That sublist will be compared against other sublists returned from this function in the future and that is how the overall linked list will be sorted.
def merge(self, left: ListNode, right:ListNode) -> ListNode:
We will start by creating a dummy node and a merger
pointer.
The dummy node is created so that we don't have to handle the initial logic of seeing which node should go first in the list.
dummy = ListNode(0)
merger = dummy
Next, we will have loop that will run while the current node in both sublists aren't None.
while left and right:
In each iteration, we will compare the two values.
If the left node is less than the right node, we will merge the left node into the output list and increment the left pointer.
if left.val < right.val:
merger.next = left
left = left.next
Otherwise, we will do the same thing but for the right node.
else:
merger.next = right
right = right.next
After we have merged the lesser node into the output list, we will increment the merger
pointer.
merger = merger.next
Once we have broken from the loop, it means that at least one node is None so we will just merge whichever node isn't None into the output list and return it with the node after the dummy node being the head.
merger.next = left or right
return dummy.next
Now we can hop back to our main recursive function, sortList
.
Since this function is recursive, we need to make sure we make our base case first, or else this function will run indefinitely.
Our goal is to cast a Depth First Search fishing hook down to break the input list into single nodes, then to have those nodes be sorted in the deepest depth of the recursive pond, begin emerging and join together while still sorting, then pop up into our boat as a glorious fifty pound sorted linked list that we can show off in a picture at the bar while wearing a flannel cut-off.
In other words, at the bottom of our recursive tree, we are only going to want a single tail node. If the node is pointing to None, our function will return the node. If the node is None, our function will return None.
if not head or not head.next:
return head
We will get the mid node which if we recall, will be the head of the right list. We will then recurse the left and right sublists until the base case is met at the bottom of the recursive tree, and then we will merge the sublists to climb back up the recursive tree with our initial recursive call returning the final, sorted linked list.
right = self.sortList(mid)
return self.merge(left,right)