Taro Logo

Sort List

Medium
Meta logo
Meta
1 view
Topics:
Linked Lists

You are given the head of a singly linked list. Your task is to sort the linked list in ascending order and return the head of the sorted list.

  • The number of nodes in the list is in the range [0, 5 * 10^4]. The value of each node will be between -10^5 and 10^5

Example 1:

Input: head = [4, 2, 1, 3]

Output: [1, 2, 3, 4]

Explanation: The input linked list has the values 4, 2, 1, and 3 in that order. The sorted linked list should have the values 1, 2, 3, and 4 in ascending order.

Example 2:

Input: head = [-1, 5, 3, 4, 0]

Output: [-1, 0, 3, 4, 5]

Explanation: The input linked list has the values -1, 5, 3, 4, and 0. The sorted linked list should have the values -1, 0, 3, 4, and 5 in ascending order.

Example 3:

Input: head = []

Output: []

Explanation: An empty linked list should return an empty linked list.

Can you sort the linked list in O(n log n) time and O(1) memory (i.e., constant space)?

Solution


Naive Approach: Using an Array

The most straightforward approach is to extract all the values from the linked list into an array, sort the array, and then reconstruct a new sorted linked list (or modify the existing one). This method is easy to implement but not the most efficient.

Steps:

  1. Traverse the linked list and store all node values in an array.
  2. Sort the array using a sorting algorithm like Arrays.sort() (which typically uses a variation of quicksort or merge sort).
  3. Create a new linked list from the sorted array or overwrite the values in the original linked list.

Code (Java):

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

class ListNode {
    int val;
    ListNode next;
    ListNode() {}
    ListNode(int val) { this.val = val; }
    ListNode(int val, ListNode next) { this.val = val; this.next = next; }
}

public class Solution {
    public ListNode sortList(ListNode head) {
        List<Integer> values = new ArrayList<>();
        ListNode current = head;
        while (current != null) {
            values.add(current.val);
            current = current.next;
        }
        
        Collections.sort(values);
        
        current = head;
        int i = 0;
        while (current != null) {
            current.val = values.get(i++);
            current = current.next;
        }
        
        return head;
    }
}

Time Complexity: O(n log n)

  • Traversing the list: O(n)
  • Sorting the array: O(n log n)
  • Reconstructing the list: O(n)

Space Complexity: O(n)

  • We use an array to store the values, which takes O(n) space.

Optimal Approach: Merge Sort

The optimal solution is to use merge sort. Merge sort is well-suited for linked lists because it doesn't require random access (unlike quicksort, which is array-friendly but not ideal for linked lists).

Steps:

  1. Divide: Split the list into two halves recursively.
  2. Conquer: Sort each half using merge sort recursively.
  3. Combine: Merge the sorted halves.

Code (Java):

class ListNode {
    int val;
    ListNode next;
    ListNode() {}
    ListNode(int val) { this.val = val; }
    ListNode(int val, ListNode next) { this.val = val; this.next = next; }
}

public class Solution {
    public ListNode sortList(ListNode head) {
        if (head == null || head.next == null)
            return head;

        ListNode mid = getMid(head);
        ListNode left = head;
        ListNode right = mid.next;
        mid.next = null;

        left = sortList(left);
        right = sortList(right);
        return merge(left, right);
    }

    private ListNode getMid(ListNode head) {
        ListNode slow = head;
        ListNode fast = head;
        while (fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
        }
        return slow;
    }

    private ListNode merge(ListNode list1, ListNode list2) {
        ListNode dummyHead = new ListNode();
        ListNode tail = dummyHead;
        while (list1 != null && list2 != null) {
            if (list1.val < list2.val) {
                tail.next = list1;
                list1 = list1.next;
            } else {
                tail.next = list2;
                list2 = list2.next;
            }
            tail = tail.next;
        }

        if (list1 != null) {
            tail.next = list1;
        } else {
            tail.next = list2;
        }

        return dummyHead.next;
    }
}

Time Complexity: O(n log n)

  • Divide: O(log n) splits
  • Merge: O(n) for each merge operation

Space Complexity: O(log n)

  • Due to the recursive calls. In the best case scenario, merge sort can be implemented in O(1) space.

Edge Cases:

  • Empty List: If the input list is empty, return null.
  • Single Node List: If the list contains only one node, it is already sorted, so return the head.
  • List with Duplicate Values: The algorithm should handle lists with duplicate values correctly.