Sort an Array - Solution
Solutions and explanations

Analysis for Sorting Algorithm with O(n log n) Time and Minimal Space

The requirement is to implement a sorting algorithm with O(n log n) time complexity while using the smallest possible space, ideally in-place.

Let’s evaluate the standard O(n log n) sorting algorithms:

  • Merge Sort:

    • Time Complexity: O(n log n)
    • Space Complexity: O(n) (due to the need for additional arrays during merging). Not in-place in its standard form.
  • Quick Sort:

    • Time Complexity: O(n log n) on average, O(n²) in worst case.
    • Space Complexity: O(log n) due to recursive stack space. Not strictly in-place since it uses call stack space, but does not use additional arrays.
  • Heap Sort:

    • Time Complexity: O(n log n)
    • Space Complexity: O(1) (in-place sorting).
      Works by transforming the input array into a heap and sorting in-place.

Conclusion:

Among the common O(n log n) sorting algorithms, Heap Sort is the only one that satisfies both conditions:

  • Guaranteed O(n log n) time.
  • Truly in-place sorting with O(1) extra space.

So, Heap Sort is the best choice for this requirement.

class Solution:
    def sortArray(self, nums: List[int]) -> List[int]:      

        heap_size = 0
        n = len(nums)      

        def max_heapify(idx):       # O(log n)
            nonlocal heap_size
            largest = idx
            left, right = 2 * idx + 1, 2 * idx + 2
            
            if left < heap_size and nums[left] > nums[largest]:
                largest = left   
            if right < heap_size and nums[right] > nums[largest]:
                largest = right
            
            if largest != idx:
                nums[largest], nums[idx] = nums[idx], nums[largest]   # swap
                max_heapify(largest)

        def build_heap():           # O(n)
            nonlocal heap_size
            heap_size = n
            for idx in range(len(nums) // 2 - 1, -1 , -1):
                max_heapify(idx)
        
        def heap_sort():            # O(n log n)
            nonlocal heap_size
            build_heap()                        
            for idx in range(n - 1, 0, -1):   # from n-1 down to 1 (leaving index 0, which ends up in correct place)
                nums[0], nums[idx] = nums[idx], nums[0]
                heap_size -= 1
                max_heapify(0)         

        heap_sort() 
        return nums

Complexity Analysis

Here, n is the number of elements in the input array.

  • Time Complexity: O(n log n)
  • Space Complexity: O(1) - inplace.

This implementation is not in-place, unlike the classic in-place heap sort.
It does not satisfy the constraint of minimal space usage, but is provided here for reference.

import heapq

class Solution:
    def sortArray(self, nums: List[int]) -> List[int]:
        heapq.heapify(nums)     # Builds entire min-heap from list (not just "heapifying" one node)
        result = []                
        while nums:
            result.append(heapq.heappop(nums))  # Fetches the minimum element each time
        return result           # Returns a new sorted list (Not in-place)

Complexity Analysis

Here, n is the number of elements in the input array.

  • Time Complexity: O(n log n)
  • Space Complexity: O(n), due to the additional space required by the result array (not in-place).