Algorithm

Quick Sort

Sorting Pattern

Quick Sort

Given an unsorted array of integers, sort the array in ascending order using the Quick Sort algorithm. Quick Sort is a divide-and-conquer algorithm that works by partitioning the array around a pivot element.

CONSTRAINTS
  • 1 ≤ nums.length ≤ 5 * 10⁴
  • -5 * 10⁴ ≤ nums[i] ≤ 5 * 10⁴
  • Space Complexity: O(log N) for recursion stack
  • Time Complexity: O(N log N) average
EXAMPLE 1
Input: nums = [10, 80, 30, 90, 40]
Output: [10, 30, 40, 80, 90]
40 is chosen as pivot. Elements smaller than 40 move left, larger move right. The sub-arrays are then recursively sorted.
EXAMPLE 2
Input: nums = [1, 2, 3, 4, 5]
Output: [1, 2, 3, 4, 5]
If the last element is always chosen as pivot in a sorted array, Quick Sort hits its worst-case O(N²) time.
EXAMPLE 3
Input: nums = [5, 4, 3, 2, 1]
Output: [1, 2, 3, 4, 5]
A reverse sorted array also poses challenges for simple pivot selection strategies.
Is there a restriction on using extra space?
Yes, we prefer an in-place solution. Using O(log N) extra space for the recursion stack is acceptable.
How should we handle large inputs that are already sorted?
The implementation should be robust enough to handle various distributions, including sorted or nearly-sorted data, without hitting worst-case performance.
Does the relative order of equal elements matter?
No, stability is not a requirement for this specific task.
The Strategy: Partitioning

Quick Sort is a Divide and Conquer algorithm that centers around the concept of partitioning. The goal is to pick a "pivot" element and rearrange the array so that:
- Every element smaller than the pivot is moved to its left.
- Every element larger than the pivot is moved to its right.
- The pivot itself ends up in its final sorted position.

The Execution: Two-Pointer Technique

To perform this partitioning in-place (without extra memory), we use a two-pointer approach known as the Lomuto Partition Scheme:
1. Pivot Selection: We pick the last element of the range as the pivot.
2. The Explorer (j): This pointer scans every element from left to right.
3. The Boundary (i): This pointer keeps track of the "wall" between elements known to be smaller than the pivot and elements yet to be checked.

Whenever the Explorer finds a value smaller than the pivot, we swap it with the element at the Boundary and move the wall forward. Once the scan is complete, we swap the pivot into its correct spot at the wall.

The Divide: Recursive Reduction

Once the pivot is locked in place, the problem is reduced into two smaller, independent sub-problems:
1. Sort the subarray to the left of the pivot.
2. Sort the subarray to the right of the pivot.

We repeat this logic recursively until every subarray is reduced to a single element (the base case). Since every pivot was moved to its correct relative position, the entire array is now guaranteed to be sorted.

When to Use Quick Sort?

Quick Sort is often the default choice in programming language standard libraries (like Java's Arrays.sort or C++'s std::sort) because:
- Cache Efficiency: It has excellent locality of reference, meaning it accesses memory that is physically close together. This makes it much faster on modern hardware compared to Merge Sort.
- Minimal Memory: It is an in-place algorithm. It only needs O(log N) space for the recursion stack, whereas Merge Sort needs O(N) extra space to store temporary halves.
- Speed: In practice, its constant factors are smaller than Heap Sort or Merge Sort, making it faster for the average random array.

Avoid it when:
- You need a stable sort (where equal elements keep their original order).
- You are sorting Linked Lists (Merge Sort is superior here as it doesn't require random access).
- You are dealing with highly skewed data and cannot use a random pivot (risk of O(N^2)).

Pseudo Code
text
FUNCTION quicksort(arr, low, high):
    IF low < high:
        p = partition(arr, low, high)
        quicksort(arr, low, p - 1)
        quicksort(arr, p + 1, high)

FUNCTION partition(arr, low, high):
    pivot = arr[high]
    i = low
    FOR j from low to high-1:
        IF arr[j] < pivot:
            SWAP(arr[i], arr[j])
            i++
    SWAP(arr[i], arr[high])
    RETURN i
Key Traits

- Stability: No (partitioning involves long-distance swaps).
- In-Place: Yes (only uses O(log N) recursion stack).
- Time Complexity: O(N log N) average, O(N²) worst case.

Interactive Strategy Visualization

Visual Intuition

Watch the quick sort process step-by-step.

5
0
3
1
8
2
4
3
2
4
STEP 1 / 18 • INIT

Choosing 2 as pivot for subarray [0...4].

O(N²) Worst Case (Fixed Pivot)
O(N log N) Average Case