Sorting Algorithms

Search & SortPriority 4 of 5 — High value — a gap here costs you roundsHigh value Updated Sep 18, 2026
Section priorityPriority 5 of 5 — Must know — expect it in almost every loopMust knowPriority 4 of 5 — High value — a gap here costs you roundsHigh valuePriority 3 of 5 — Worth knowing — usually a variant of a must-know patternWorth knowingPriority 2 of 5 — Niche — read once, revisit only if a company is known to askNicheMarked on the sections that carry it — unmarked sections are background/reference.

Scope — Sorting algorithms and sort-adjacent techniques — comparison sorts and their stability, counting/bucket/radix, quickselect, custom comparators, and cyclic sort. See also: heap.md — heap sort and top-k; binary_search.md — what sorting enables; advanced_divide_and_conquer.md — merge sort used to count, not to sort; greedy.md — sort-then-scan.

LeetCode Problem Lists

Overview

Sorting is the process of arranging elements in a specific order (ascending or descending). It’s fundamental to many algorithms and data structures, enabling efficient searching, data analysis, and problem-solving.

Key Properties

  • Stability: Maintains relative order of equal elements
  • In-place: Uses O(1) extra space
  • Adaptive: Performs better on partially sorted data
  • When to Use: Data ordering, preprocessing for binary search, finding medians/percentiles

Algorithm Selection Guide

  • Small datasets (n < 50): Insertion Sort
  • General purpose: Quick Sort, Merge Sort
  • Guaranteed O(n log n): Heap Sort, Merge Sort
  • Nearly sorted: Insertion Sort, Bubble Sort
  • Limited range: Counting Sort, Radix Sort

References

Sorting Algorithm Time Complexity (Best Case) Time Complexity (Average Case) Time Complexity (Worst Case) Space Complexity
Bubble Sort O(n) O(n²) O(n²) O(1)
Insertion Sort O(n) O(n²) O(n²) O(1)
Selection Sort O(n²) O(n²) O(n²) O(1)
Merge Sort O(n log n) O(n log n) O(n log n) O(n)
Quick Sort O(n log n) O(n log n) O(n²) O(log n)
Heap Sort O(n log n) O(n log n) O(n log n) O(1)
Counting Sort O(n + k) O(n + k) O(n + k) O(k)
Radix Sort O(nk) O(nk) O(nk) O(n + k)
Bucket Sort O(n + k) O(n + k) O(n²) O(n)

Problem Categories

Pattern 1: Custom Comparator Sorting — LC 179

  • Description: Sort with custom rules or multiple criteria
  • Examples: LC 179, 791, 937, 1029, 1366
  • Pattern: Define comparison function for complex ordering

Pattern 2: Topological Sorting — LC 207

  • Description: Order elements based on dependencies
  • Examples: LC 207, 210, 269, 310, 1136
  • Pattern: DFS/BFS with in-degree tracking

Pattern 3: Interval Sorting — LC 56

  • Description: Sort intervals for merging/processing
  • Examples: LC 56, 57, 252, 253, 435
  • Pattern: Sort by start, then process

Pattern 4: K-th Element — LC 215

  • Description: Find k-th smallest/largest efficiently
  • Examples: LC 215, 347, 378, 658, 973
  • Pattern: Quick Select or Heap

Pattern 5: Bucket/Counting Sort — LC 164

  • Description: Sort with limited value range
  • Examples: LC 164, 274, 451, 1122, 1636
  • Pattern: Use value as index

Pattern 6: Merge Sort Applications — LC 148

  • Description: Divide-and-conquer with sorting
  • Examples: LC 23, 148, 315, 327, 493
  • Pattern: Merge sorted sequences

Pattern 7: Greedy Pairing (Sort + Two Pointers) — LC 1877

  • Description: Sort then pair smallest with largest to balance pair sums and minimize the maximum
  • Core idea: Pairing large numbers together creates unnecessarily large sums; pairing extremes (min+max) distributes weight evenly
  • Examples: LC 1877, 561, 881, 2491
  • Pattern: Sort → two pointers from both ends → track max/min of pair results

Templates & Algorithms

Algorithm Comparison Table

Algorithm Best Average Worst Space Stable When to Use
Quick Sort O(n log n) O(n log n) O(n²) O(log n) No General purpose
Merge Sort O(n log n) O(n log n) O(n log n) O(n) Yes Stable, guaranteed O(n log n)
Heap Sort O(n log n) O(n log n) O(n log n) O(1) No In-place, guaranteed O(n log n)
Insertion Sort O(n) O(n²) O(n²) O(1) Yes Small or nearly sorted
Counting Sort O(n+k) O(n+k) O(n+k) O(k) Yes Limited range integers
Radix Sort O(nk) O(nk) O(nk) O(n+k) Yes Fixed-width integers

Template 1: Quick Sort

python
# Python - Classic Quick Sort
def quickSort(arr, low=0, high=None):
    if high is None:
        high = len(arr) - 1
    
    if low < high:
        # Partition and get pivot index
        pi = partition(arr, low, high)
        
        # Recursively sort left and right
        quickSort(arr, low, pi - 1)
        quickSort(arr, pi + 1, high)
    
    return arr

def partition(arr, low, high):
    # Choose rightmost as pivot
    pivot = arr[high]
    i = low - 1  # Smaller element index
    
    for j in range(low, high):
        if arr[j] <= pivot:
            i += 1
            arr[i], arr[j] = arr[j], arr[i]
    
    arr[i + 1], arr[high] = arr[high], arr[i + 1]
    return i + 1

# 3-way Quick Sort for duplicates
def quickSort3Way(arr, low=0, high=None):
    if high is None:
        high = len(arr) - 1
    
    if low < high:
        lt, gt = partition3Way(arr, low, high)
        quickSort3Way(arr, low, lt - 1)
        quickSort3Way(arr, gt + 1, high)
    
    return arr

def partition3Way(arr, low, high):
    pivot = arr[low]
    i = low
    lt = low
    gt = high
    
    while i <= gt:
        if arr[i] < pivot:
            arr[lt], arr[i] = arr[i], arr[lt]
            lt += 1
            i += 1
        elif arr[i] > pivot:
            arr[i], arr[gt] = arr[gt], arr[i]
            gt -= 1
        else:
            i += 1
    
    return lt, gt
java
// Java - Quick Sort
public void quickSort(int[] arr, int low, int high) {
    if (low < high) {
        int pi = partition(arr, low, high);
        quickSort(arr, low, pi - 1);
        quickSort(arr, pi + 1, high);
    }
}

private int partition(int[] arr, int low, int high) {
    int pivot = arr[high];
    int i = low - 1;
    
    for (int j = low; j < high; j++) {
        if (arr[j] <= pivot) {
            i++;
            swap(arr, i, j);
        }
    }
    
    swap(arr, i + 1, high);
    return i + 1;
}

private void swap(int[] arr, int i, int j) {
    int temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}

Variation — run partition3Way ONCE (Dutch National Flag) — LC 75

Twist: when the alphabet is a fixed tiny set ({0,1,2}), you don’t recurse — a single 3-way partition pass fully sorts the array in O(n) / O(1).

java
// java
// LC 75 - Sort Colors
// time = O(n), space = O(1)
// IDEA: Dutch National Flag — 3 pointers lo / i / hi.
//   Invariant:  [0, lo)  == 0   |   [lo, i) == 1   |   (hi, n-1] == 2
//   KEY TRAP: after swapping with `hi`, do NOT advance i — the value pulled
//             in from the back has not been examined yet.
public void sortColors(int[] nums) {
    int lo = 0, i = 0, hi = nums.length - 1;
    while (i <= hi) {
        if (nums[i] == 0) {
            int t = nums[lo]; nums[lo] = nums[i]; nums[i] = t;
            lo++; i++;                 // safe: nums[lo] was a 1 (already seen)
        } else if (nums[i] == 2) {
            int t = nums[hi]; nums[hi] = nums[i]; nums[i] = t;
            hi--;                      // NOTE: i stays put
        } else {
            i++;
        }
    }
}
python
# python
# LC 75 - Sort Colors
# time = O(n), space = O(1)
# IDEA: Dutch National Flag one-pass 3-way partition
class Solution:
    def sortColors(self, nums):
        lo, i, hi = 0, 0, len(nums) - 1
        while i <= hi:
            if nums[i] == 0:
                nums[lo], nums[i] = nums[i], nums[lo]
                lo += 1
                i += 1
            elif nums[i] == 2:
                nums[hi], nums[i] = nums[i], nums[hi]
                hi -= 1          # NOTE: do NOT advance i here
            else:
                i += 1

Follow-up often asked: “can you do it without counting sort (two passes)?” → the DNF one-pass above is the expected answer.

Template 2: Merge Sort

python
# Python - Merge Sort
def mergeSort(arr):
    if len(arr) <= 1:
        return arr
    
    mid = len(arr) // 2
    left = mergeSort(arr[:mid])
    right = mergeSort(arr[mid:])
    
    return merge(left, right)

def merge(left, right):
    result = []
    i = j = 0
    
    while i < len(left) and j < len(right):
        if left[i] <= right[j]:
            result.append(left[i])
            i += 1
        else:
            result.append(right[j])
            j += 1
    
    result.extend(left[i:])
    result.extend(right[j:])
    return result

# In-place merge sort
def mergeSortInPlace(arr, left=0, right=None):
    if right is None:
        right = len(arr) - 1
    
    if left < right:
        mid = (left + right) // 2
        mergeSortInPlace(arr, left, mid)
        mergeSortInPlace(arr, mid + 1, right)
        mergeInPlace(arr, left, mid, right)
    
    return arr

def mergeInPlace(arr, left, mid, right):
    left_arr = arr[left:mid + 1]
    right_arr = arr[mid + 1:right + 1]
    
    i = j = 0
    k = left
    
    while i < len(left_arr) and j < len(right_arr):
        if left_arr[i] <= right_arr[j]:
            arr[k] = left_arr[i]
            i += 1
        else:
            arr[k] = right_arr[j]
            j += 1
        k += 1
    
    while i < len(left_arr):
        arr[k] = left_arr[i]
        i += 1
        k += 1
    
    while j < len(right_arr):
        arr[k] = right_arr[j]
        j += 1
        k += 1
java
// Java - Merge Sort
public void mergeSort(int[] arr, int left, int right) {
    if (left < right) {
        int mid = left + (right - left) / 2;
        
        mergeSort(arr, left, mid);
        mergeSort(arr, mid + 1, right);
        
        merge(arr, left, mid, right);
    }
}

private void merge(int[] arr, int left, int mid, int right) {
    int n1 = mid - left + 1;
    int n2 = right - mid;
    
    int[] leftArr = new int[n1];
    int[] rightArr = new int[n2];
    
    for (int i = 0; i < n1; i++) {
        leftArr[i] = arr[left + i];
    }
    for (int j = 0; j < n2; j++) {
        rightArr[j] = arr[mid + 1 + j];
    }
    
    int i = 0, j = 0, k = left;
    
    while (i < n1 && j < n2) {
        if (leftArr[i] <= rightArr[j]) {
            arr[k++] = leftArr[i++];
        } else {
            arr[k++] = rightArr[j++];
        }
    }
    
    while (i < n1) {
        arr[k++] = leftArr[i++];
    }
    
    while (j < n2) {
        arr[k++] = rightArr[j++];
    }
}

Variation — merge in place by walking BACKWARDS — LC 88

Twist: nums1 already owns the trailing free space, so there is no room for the O(n) buffer the standard merge uses. Filling from the tail (largest first) means every slot you write is either free or already consumed → O(1) extra space.

java
// java
// LC 88 - Merge Sorted Array
// time = O(m + n), space = O(1)
// IDEA: write from the back. k = last free slot, i/j = last real elems.
//   Loop on `j` only: if nums2 is exhausted, the nums1 prefix is already in place.
public void merge(int[] nums1, int m, int[] nums2, int n) {
    int i = m - 1, j = n - 1, k = m + n - 1;
    while (j >= 0) {
        if (i >= 0 && nums1[i] > nums2[j]) {
            nums1[k--] = nums1[i--];
        } else {
            nums1[k--] = nums2[j--];
        }
    }
}
python
# python
# LC 88 - Merge Sorted Array
# time = O(m + n), space = O(1)
# IDEA: fill nums1 from the tail so we never overwrite an unread element
class Solution:
    def merge(self, nums1, m, nums2, n):
        i, j, k = m - 1, n - 1, m + n - 1
        while j >= 0:
            if i >= 0 and nums1[i] > nums2[j]:
                nums1[k] = nums1[i]
                i -= 1
            else:
                nums1[k] = nums2[j]
                j -= 1
            k -= 1

Why backwards? Merging forwards would overwrite nums1[0..m-1] before reading it, forcing an O(m) copy. This “fill from the largest end” trick reappears in any in-place merge (e.g. the merge step of LC 148 Sort List uses pointer relinking for the same reason).

Template 3: Custom Comparator Sorting — LC 179

python
# Python - Custom sorting
class Solution:
    def customSort(self, items):
        # Single key
        items.sort(key=lambda x: x[0])
        
        # Multiple keys
        items.sort(key=lambda x: (x[0], -x[1], x[2]))
        
        # Complex comparison
        def compare(item):
            # Return tuple of sort keys
            if condition:
                return (0, item.value, item.name)
            else:
                return (1, -item.priority, item.id)
        
        items.sort(key=compare)
        
        # Using functools for traditional comparison
        from functools import cmp_to_key
        
        def compare_func(a, b):
            if a < b:
                return -1
            elif a > b:
                return 1
            else:
                return 0
        
        items.sort(key=cmp_to_key(compare_func))
        
        return items

# Custom class for sorting
class CustomComparable:
    def __init__(self, value, priority):
        self.value = value
        self.priority = priority
    
    def __lt__(self, other):
        # Define less than for sorting
        if self.priority != other.priority:
            return self.priority > other.priority
        return self.value < other.value
java
// Java - Custom comparator
public void customSort(List<Item> items) {
    // Lambda comparator
    items.sort((a, b) -> a.value - b.value);
    
    // Multiple criteria
    items.sort((a, b) -> {
        if (a.priority != b.priority) {
            return b.priority - a.priority;  // Descending
        }
        return a.name.compareTo(b.name);    // Ascending
    });
    
    // Using Comparator methods
    items.sort(Comparator
        .comparingInt(Item::getPriority).reversed()
        .thenComparing(Item::getName));
    
    // Custom Comparator class
    items.sort(new Comparator<Item>() {
        @Override
        public int compare(Item a, Item b) {
            // Custom logic
            return customCompare(a, b);
        }
    });
}

// Comparable interface
class Item implements Comparable<Item> {
    int value;
    String name;
    
    @Override
    public int compareTo(Item other) {
        if (this.value != other.value) {
            return this.value - other.value;
        }
        return this.name.compareTo(other.name);
    }
}

Template 4: Quick Select (K-th Element) — LC 215

python
# Python - Quick Select for k-th smallest
def quickSelect(arr, k):
    # Find k-th smallest (0-indexed)
    return quickSelectHelper(arr, 0, len(arr) - 1, k - 1)

def quickSelectHelper(arr, left, right, k):
    if left == right:
        return arr[left]
    
    # Random pivot for better average case
    import random
    pivot_idx = random.randint(left, right)
    pivot_idx = partition(arr, left, right, pivot_idx)
    
    if k == pivot_idx:
        return arr[k]
    elif k < pivot_idx:
        return quickSelectHelper(arr, left, pivot_idx - 1, k)
    else:
        return quickSelectHelper(arr, pivot_idx + 1, right, k)

def partition(arr, left, right, pivot_idx):
    pivot = arr[pivot_idx]
    # Move pivot to end
    arr[pivot_idx], arr[right] = arr[right], arr[pivot_idx]
    
    store_idx = left
    for i in range(left, right):
        if arr[i] < pivot:
            arr[store_idx], arr[i] = arr[i], arr[store_idx]
            store_idx += 1
    
    # Move pivot to final position
    arr[store_idx], arr[right] = arr[right], arr[store_idx]
    return store_idx

Template 5: Counting Sort

python
# Python - Counting Sort
def countingSort(arr, max_val=None):
    if not arr:
        return arr
    
    if max_val is None:
        max_val = max(arr)
    min_val = min(arr)
    
    # Create counting array
    range_size = max_val - min_val + 1
    count = [0] * range_size
    
    # Count occurrences
    for num in arr:
        count[num - min_val] += 1
    
    # Reconstruct sorted array
    idx = 0
    for i in range(range_size):
        while count[i] > 0:
            arr[idx] = i + min_val
            idx += 1
            count[i] -= 1
    
    return arr

# Stable counting sort
def stableCountingSort(arr):
    if not arr:
        return arr
    
    max_val = max(arr)
    min_val = min(arr)
    range_size = max_val - min_val + 1
    
    count = [0] * range_size
    output = [0] * len(arr)
    
    # Count occurrences
    for num in arr:
        count[num - min_val] += 1
    
    # Cumulative count
    for i in range(1, range_size):
        count[i] += count[i - 1]
    
    # Build output array (stable)
    for i in range(len(arr) - 1, -1, -1):
        output[count[arr[i] - min_val] - 1] = arr[i]
        count[arr[i] - min_val] -= 1
    
    return output

Template 6: Topological Sort — LC 207

python
# Python - Topological Sort (Kahn's Algorithm)
def topologicalSort(numNodes, edges):
    # Build graph and in-degree
    graph = defaultdict(list)
    in_degree = [0] * numNodes
    
    for u, v in edges:
        graph[u].append(v)
        in_degree[v] += 1
    
    # Queue for nodes with no dependencies
    queue = deque([i for i in range(numNodes) if in_degree[i] == 0])
    result = []
    
    while queue:
        node = queue.popleft()
        result.append(node)
        
        for neighbor in graph[node]:
            in_degree[neighbor] -= 1
            if in_degree[neighbor] == 0:
                queue.append(neighbor)
    
    # Check for cycle
    if len(result) != numNodes:
        return []  # Cycle detected
    
    return result

# DFS-based Topological Sort
def topologicalSortDFS(numNodes, edges):
    graph = defaultdict(list)
    for u, v in edges:
        graph[u].append(v)
    
    visited = set()
    stack = []
    
    def dfs(node):
        visited.add(node)
        for neighbor in graph[node]:
            if neighbor not in visited:
                dfs(neighbor)
        stack.append(node)
    
    for i in range(numNodes):
        if i not in visited:
            dfs(i)
    
    return stack[::-1]

Template 7: Merge Sort as a COUNTER (inversions / smaller-after-self) — LC 315 Priority 5 of 5 — Must know — expect it in almost every loop

Key Idea: the merge step is the only moment where a whole block of “right-half” elements is known to be smaller than a given left-half element. Piggyback a counter on that moment and you count pairs across the array in O(n log n) instead of O(n²).

Recurrence: answer(lo..hi) = answer(left) + answer(right) + cross-pairs counted during merge

Critical detail: you must sort an array of indices, not the values — the answer is reported per original position, and the values get shuffled by the sort.

java
// java
// LC 315 - Count of Smaller Numbers After Self
// time = O(n log n), space = O(n)
// IDEA: merge sort over an INDEX array. While merging, `moved` = how many
//       right-half elements have already been emitted. Every time we emit a
//       LEFT element, exactly `moved` smaller elements sat after it → add.
public List<Integer> countSmaller(int[] nums) {
    int n = nums.length;
    int[] count = new int[n];
    int[] idx = new int[n];
    for (int i = 0; i < n; i++) idx[i] = i;

    sortCount(nums, idx, new int[n], 0, n - 1, count);

    List<Integer> res = new ArrayList<>();
    for (int c : count) res.add(c);
    return res;
}

private void sortCount(int[] nums, int[] idx, int[] tmp, int lo, int hi, int[] count) {
    if (lo >= hi) return;
    int mid = lo + (hi - lo) / 2;
    sortCount(nums, idx, tmp, lo, mid, count);
    sortCount(nums, idx, tmp, mid + 1, hi, count);

    int i = lo, j = mid + 1, k = lo;
    int moved = 0;                          // # of right-half elems already merged
    while (i <= mid && j <= hi) {
        if (nums[idx[j]] < nums[idx[i]]) {  // strict `<` keeps the sort stable
            moved++;
            tmp[k++] = idx[j++];
        } else {
            count[idx[i]] += moved;         // <-- the whole trick
            tmp[k++] = idx[i++];
        }
    }
    while (i <= mid) { count[idx[i]] += moved; tmp[k++] = idx[i++]; }
    while (j <= hi)  { tmp[k++] = idx[j++]; }
    for (int t = lo; t <= hi; t++) idx[t] = tmp[t];
}
python
# python
# LC 315 - Count of Smaller Numbers After Self
# time = O(n log n), space = O(n)
# IDEA: merge sort the INDEX list; `moved` counts right-half elements already
#       emitted, which are exactly the smaller-and-to-the-right ones.
class Solution:
    def countSmaller(self, nums):
        n = len(nums)
        count = [0] * n
        idx = list(range(n))

        def sort_count(lo, hi):
            if lo >= hi:
                return
            mid = (lo + hi) // 2
            sort_count(lo, mid)
            sort_count(mid + 1, hi)

            i, j, moved, tmp = lo, mid + 1, 0, []
            while i <= mid and j <= hi:
                if nums[idx[j]] < nums[idx[i]]:   # right elem is smaller
                    moved += 1
                    tmp.append(idx[j]); j += 1
                else:
                    count[idx[i]] += moved        # <-- the whole trick
                    tmp.append(idx[i]); i += 1
            while i <= mid:
                count[idx[i]] += moved
                tmp.append(idx[i]); i += 1
            while j <= hi:
                tmp.append(idx[j]); j += 1
            idx[lo:hi + 1] = tmp

        sort_count(0, n - 1)
        return count

Visual tracenums = [5,2,6,1], final merge of [2,5] (idx 1,0) and [1,6] (idx 3,2):

text
left = [2(i1), 5(i0)]      right = [1(i3), 6(i2)]      moved = 0
 step 1: 1 < 2   -> emit right, moved = 1
 step 2: 2 <= 6  -> emit left  , count[1] += 1   -> count[1] = 1
 step 3: 5 <= 6  -> emit left  , count[0] += 1   -> count[0] = 2 (1 came from the earlier level)
 step 4: drain right
answer = [2,1,1,0]

Same skeleton, different counting predicate:

Problem LC # What you count during merge
Count of Smaller Numbers After Self 315 right elems < left elem
Reverse Pairs 493 pairs with left > 2 * right (extra pre-scan before merging)
Count of Range Sum 327 prefix-sum pairs whose difference lands in [lower, upper]

Template 8: Bucket Sort by Value Range (bucket = value / width) — LC 220 Priority 4 of 5 — High value — a gap here costs you rounds

Key Idea: when the question is “do two values differ by at most t?”, make buckets of width t + 1. Then:

  • two values in the same bucket always differ by ≤ t → instant hit
  • values more than t apart can only be in adjacent buckets → you only ever check id-1, id, id+1

That turns an O(n log k) balanced-BST / sliding-window-sort solution into O(n).

java
// java
// LC 220 - Contains Duplicate III
// time = O(n), space = O(min(n, indexDiff))
// IDEA: bucket width = valueDiff + 1, so "same bucket" == "within valueDiff".
//       Keep only the last `indexDiff` elements as a sliding window of buckets.
public boolean containsNearbyAlmostDuplicate(int[] nums, int indexDiff, int valueDiff) {
    if (indexDiff <= 0 || valueDiff < 0) return false;
    long w = (long) valueDiff + 1;                 // NOTE: long, valueDiff can be MAX_VALUE
    Map<Long, Long> bucket = new HashMap<>();

    for (int i = 0; i < nums.length; i++) {
        long id = bucketId(nums[i], w);
        if (bucket.containsKey(id)) return true;                                        // same bucket
        if (bucket.containsKey(id - 1) && nums[i] - bucket.get(id - 1) <= valueDiff) return true;
        if (bucket.containsKey(id + 1) && bucket.get(id + 1) - nums[i] <= valueDiff) return true;

        bucket.put(id, (long) nums[i]);            // at most 1 value per bucket in the window
        if (i >= indexDiff) bucket.remove(bucketId(nums[i - indexDiff], w));  // slide
    }
    return false;
}

// KEY TRAP: Java integer division truncates toward zero, so -1/3 == 0 == 2/3.
//           Negative values need an explicit floor.
private long bucketId(long x, long w) {
    return x < 0 ? (x + 1) / w - 1 : x / w;
}
python
# python
# LC 220 - Contains Duplicate III
# time = O(n), space = O(min(n, indexDiff))
# IDEA: bucket width = valueDiff + 1; check own bucket + 2 neighbours only.
#       Python's // already floors, so negatives need no special case.
class Solution:
    def containsNearbyAlmostDuplicate(self, nums, indexDiff, valueDiff):
        if indexDiff <= 0 or valueDiff < 0:
            return False
        w = valueDiff + 1
        bucket = {}
        for i, x in enumerate(nums):
            bid = x // w
            if bid in bucket:
                return True
            if bid - 1 in bucket and x - bucket[bid - 1] <= valueDiff:
                return True
            if bid + 1 in bucket and bucket[bid + 1] - x <= valueDiff:
                return True
            bucket[bid] = x
            if i >= indexDiff:                     # slide the window
                bucket.pop(nums[i - indexDiff] // w, None)
        return False

Bucket-width design cheat sheet (this is the reusable part):

Goal Bucket width Why
“two values within t” (LC 220) t + 1 same bucket ⇒ diff ≤ t; only neighbours can also qualify
“max gap between consecutive sorted values” (LC 164) (max-min)/(n-1) pigeonhole ⇒ the max gap must be between buckets, so intra-bucket order is irrelevant
“top K by frequency” (LC 347) freq as index, 1..n frequency range is bounded by n ⇒ index directly

Template 9: Sort by a DERIVED Key to Unlock Greedy / DP — LC 354 Priority 5 of 5 — Must know — expect it in almost every loop

Key Idea: many “2-D” problems collapse to a solved 1-D problem once you pick the right sort order. The sort itself is the algorithm; the tie-break rule is where the interview is won or lost.

Pattern: sort on the first dimension ascending, and on ties sort the second dimension descending — the descending tie-break makes equal-first-dimension items mutually non-chainable, so a plain strictly-increasing scan on dimension 2 is automatically correct.

java
// java
// LC 354 - Russian Doll Envelopes
// time = O(n log n), space = O(n)
// IDEA: sort width ASC, height DESC on ties -> answer = LIS over heights.
//   WHY height DESC? envelopes [3,5] and [3,7] must never both be chosen.
//   With height DESC they appear as 7 then 5 (decreasing), so no increasing
//   subsequence can pick both. With height ASC you'd wrongly nest them.
public int maxEnvelopes(int[][] envelopes) {
    Arrays.sort(envelopes, (a, b) -> a[0] == b[0] ? b[1] - a[1] : a[0] - b[0]);

    int[] tails = new int[envelopes.length];   // tails[l] = min tail of an LIS of length l+1
    int len = 0;
    for (int[] e : envelopes) {
        int i = Arrays.binarySearch(tails, 0, len, e[1]);
        if (i < 0) i = -(i + 1);               // insertion point = lower_bound
        tails[i] = e[1];
        if (i == len) len++;
    }
    return len;
}
python
# python
# LC 354 - Russian Doll Envelopes
# time = O(n log n), space = O(n)
# IDEA: sort (w ASC, h DESC) -> strictly-increasing LIS over heights
import bisect
class Solution:
    def maxEnvelopes(self, envelopes):
        # NOTE: -e[1] is the whole trick (blocks same-width nesting)
        envelopes.sort(key=lambda e: (e[0], -e[1]))
        tails = []
        for _, h in envelopes:
            i = bisect.bisect_left(tails, h)   # bisect_left => STRICTLY increasing
            if i == len(tails):
                tails.append(h)
            else:
                tails[i] = h
        return len(tails)

Variation — sort by derived key, then INSERT greedily — LC 406

Twist: sort tallest-first so that everyone already placed is ≥ the current person; then k is literally the index to insert at, because shorter people inserted later never disturb an earlier person’s count.

java
// java
// LC 406 - Queue Reconstruction by Height
// time = O(n^2) (list insert), space = O(n)
// IDEA: height DESC, k ASC. Insert person at index k — everyone already in
//       the list is taller/equal, so exactly k of them end up in front.
public int[][] reconstructQueue(int[][] people) {
    Arrays.sort(people, (a, b) -> a[0] == b[0] ? a[1] - b[1] : b[0] - a[0]);
    List<int[]> res = new ArrayList<>();
    for (int[] p : people) res.add(p[1], p);
    return res.toArray(new int[0][]);
}
python
# python
# LC 406 - Queue Reconstruction by Height
# time = O(n^2), space = O(n)
# IDEA: sort (-h, k) then insert each person at index k
class Solution:
    def reconstructQueue(self, people):
        people.sort(key=lambda p: (-p[0], p[1]))
        res = []
        for p in people:
            res.insert(p[1], p)
        return res

Variation — sort by derived key, then DP over predecessors — LC 1048

Twist: sort by length so that every possible predecessor of a word is guaranteed to be processed before it — the DP then needs no recursion or memo ordering logic at all.

java
// java
// LC 1048 - Longest String Chain
// time = O(n * L^2), space = O(n * L)   (L = max word length)
// IDEA: sort by length -> predecessors always come first. For each word try
//       deleting each char and look the shorter word up in the dp map.
public int longestStrChain(String[] words) {
    Arrays.sort(words, (a, b) -> a.length() - b.length());
    Map<String, Integer> dp = new HashMap<>();
    int best = 0;
    for (String w : words) {
        int cur = 1;
        for (int i = 0; i < w.length(); i++) {
            String pre = w.substring(0, i) + w.substring(i + 1);
            cur = Math.max(cur, dp.getOrDefault(pre, 0) + 1);
        }
        dp.put(w, cur);
        best = Math.max(best, cur);
    }
    return best;
}
python
# python
# LC 1048 - Longest String Chain
# time = O(n * L^2), space = O(n * L)
# IDEA: sort by length so predecessors are already in dp when we reach a word
class Solution:
    def longestStrChain(self, words):
        words.sort(key=len)
        dp = {}
        best = 0
        for w in words:
            cur = 1
            for i in range(len(w)):
                cur = max(cur, dp.get(w[:i] + w[i + 1:], 0) + 1)
            dp[w] = cur
            best = max(best, cur)
        return best

Derived-key sort order — quick decision table:

Situation Sort order Unlocked by
2-D strict nesting (LC 354) dim1 ASC, dim2 DESC on ties LIS on dim2
Place items where “count of bigger” matters (LC 406) height DESC, k ASC insert at index k
DP where predecessor is “smaller” (LC 1048) by size/length ASC forward DP, no memo
Compare-by-concatenation (LC 179) custom a+b vs b+a direct join

⚠️ Comparator transitivity trap: a custom comparator must be a total ordercompare(a,b) > 0 && compare(b,c) > 0 must imply compare(a,c) > 0. Java’s TimSort throws IllegalArgumentException: Comparison method violates its general contract! when it isn’t. LC 179’s a+b vs b+a rule is provably transitive; ad-hoc rules like “sort by whichever field is nonzero” usually are not. Also prefer Integer.compare(a, b) over a - b (overflow on large/negative values).


Template 10: Cyclic Sort (values are a permutation of 1..n) — LC 645 Priority 4 of 5 — High value — a gap here costs you rounds

Key Idea: when the values themselves are the indices (1..n or 0..n-1), you don’t need a comparison sort at all — repeatedly swap each value home to index = value - 1. Every swap places one value permanently, so the total work is O(n) with O(1) space. Afterwards, any index whose value is wrong pinpoints the missing/duplicate/misplaced element.

java
// java
// LC 645 - Set Mismatch
// time = O(n), space = O(1)
// IDEA: cyclic sort — send nums[i] to index nums[i]-1.
//   LOOP CONDITION: use `nums[i] != nums[nums[i]-1]` (compare VALUES), not
//   `nums[i] != i+1`. With duplicates the "home" slot is already taken, and
//   comparing values is what stops the swap loop from spinning forever.
public int[] findErrorNums(int[] nums) {
    int n = nums.length;
    for (int i = 0; i < n; i++) {
        while (nums[i] != nums[nums[i] - 1]) {
            int t = nums[i];
            nums[i] = nums[t - 1];
            nums[t - 1] = t;
        }
    }
    // now nums[i] should be i+1; the one that isn't gives both answers
    for (int i = 0; i < n; i++) {
        if (nums[i] != i + 1) return new int[]{nums[i], i + 1};  // {duplicated, missing}
    }
    return new int[]{-1, -1};
}
python
# python
# LC 645 - Set Mismatch
# time = O(n), space = O(1)
# IDEA: cyclic sort, then scan for the index whose value is not i+1
class Solution:
    def findErrorNums(self, nums):
        n = len(nums)
        for i in range(n):
            # NOTE: compare VALUES (not nums[i] != i+1) so duplicates terminate
            while nums[i] != nums[nums[i] - 1]:
                t = nums[i]
                nums[i], nums[t - 1] = nums[t - 1], nums[i]
        for i in range(n):
            if nums[i] != i + 1:
                return [nums[i], i + 1]   # [duplicated, missing]
        return [-1, -1]

When to reach for cyclic sort: the array length is n and the values are constrained to 1..n (or 0..n-1), and the follow-up asks for O(n) time / O(1) space (so no HashSet, no counting array). The post-sort scan is what varies — “first index that’s wrong” answers missing/duplicate/first-missing-positive style questions.

Template 11: Offline Queries — sort the QUERIES too, then sweep — LC 1847 Priority 4 of 5 — High value — a gap here costs you rounds

When: every query is answered independently, all of them are given up front, and answering one in isolation costs too much. Sorting the queries alongside the data turns q independent searches into one sweep — but only if you remember to put the answers back in the original order.

text
online   : answer query i the moment it arrives           (must handle any order)
offline  : you have all q queries -> reorder them freely  (this template)

The recipe is always these four steps:

  1. Sort the data by the dimension the queries filter on.
  2. Sort the query indices by the same dimension — keep the index, that is the whole trick.
  3. Sweep: a single moving pointer feeds items into a searchable structure as the threshold relaxes.
  4. Write each answer into ans[originalIndex].

LC 1847 Closest Room: each query is (preferred id, minSize) — among rooms with size >= minSize, return the id closest to preferred, ties going to the smaller id.

  • Sort rooms by size descending and queries by minSize descending. As minSize relaxes, rooms only ever get added — never removed. A monotone threshold is what makes one pointer enough.
  • The structure must answer “nearest value to x”, so it needs order: a TreeSet in Java (floor / ceiling), a SortedList in Python.
java
// java
// LC 1847 - Closest Room
// IDEA: offline. Rooms by size DESC, queries by minSize DESC; one pointer adds rooms into
//       a TreeSet as the size requirement relaxes, then floor/ceiling gives the nearest id.
// time = O(n log n + q log q + q log n), space = O(n + q)
public int[] closestRoom(int[][] rooms, int[][] queries) {
    int n = rooms.length, q = queries.length;
    Arrays.sort(rooms, (a, b) -> b[1] - a[1]);            // size descending

    Integer[] order = new Integer[q];
    for (int i = 0; i < q; i++) order[i] = i;
    // NOTE !!! sort the INDICES, not the queries — ans must go back in the caller's order
    Arrays.sort(order, (a, b) -> queries[b][1] - queries[a][1]);

    TreeSet<Integer> ids = new TreeSet<>();
    int[] ans = new int[q];
    int j = 0;

    for (int qi : order) {
        int preferred = queries[qi][0], minSize = queries[qi][1];
        while (j < n && rooms[j][1] >= minSize) ids.add(rooms[j++][0]);   // monotone: add only

        Integer lo = ids.floor(preferred), hi = ids.ceiling(preferred);
        if (lo == null && hi == null)      ans[qi] = -1;
        else if (lo == null)               ans[qi] = hi;
        else if (hi == null)               ans[qi] = lo;
        // tie -> smaller id, so `<=` favours the floor
        else ans[qi] = (preferred - lo <= hi - preferred) ? lo : hi;
    }
    return ans;
}
python
# python
# LC 1847 - Closest Room
# IDEA: same sweep. `ids` is kept sorted so bisect finds the two candidates around
#       `preferred`; insort keeps this to the standard library, at O(n) per insert.
# time = O(n^2 + q log q + q log n), space = O(n + q)
from bisect import bisect_left, insort

def closestRoom(rooms, queries):
    rooms = sorted(rooms, key=lambda r: -r[1])                  # size descending
    order = sorted(range(len(queries)), key=lambda i: -queries[i][1])

    ids = []
    ans = [-1] * len(queries)
    j = 0

    for qi in order:
        preferred, min_size = queries[qi]
        while j < len(rooms) and rooms[j][1] >= min_size:
            insort(ids, rooms[j][0])                            # O(n) memmove per insert
            j += 1
        if not ids:
            continue                                            # no room is big enough

        k = bisect_left(ids, preferred)
        best = None
        for cand in (ids[k - 1] if k > 0 else None, ids[k] if k < len(ids) else None):
            if cand is None:
                continue
            # strict `<` keeps the smaller id on a tie, because the floor is seen first
            if best is None or abs(cand - preferred) < abs(best - preferred):
                best = cand
        ans[qi] = best

    return ans

insort vs a real balanced set. bisect.insort finds the slot in O(log n) but shifts the tail, so each insert is O(n) and the sweep is O(n^2) — fine for LC 1847’s n <= 10^5 because the shift is a memmove, and it needs nothing outside the standard library. The O(n log n) version is SortedList from the third-party sortedcontainers package (pre-installed on LeetCode, not in the standard library, so pip install sortedcontainers to run it here): swap ids = [] for ids = SortedList(), insort(ids, x) for ids.add(x), and bisect_left(ids, x) for ids.bisect_left(x). Java’s TreeSet above is the genuinely logarithmic structure and needs no such caveat.

Common mistakes

  • Sorting the queries themselves. The answers then come out in sorted order and the judge sees a permutation of the right array. Sort an index array, or carry the index in the tuple.
  • A non-monotone threshold. If items had to be removed as the sweep advances, one pointer is not enough — you need a structure that supports deletion, or two sweeps.
  • Only checking ceiling. The nearest value can be on either side; both neighbours must be compared, and the tie rule read off the statement.

Where else this template shows up

Problem Sort by Structure swept into
LC 1847 Closest Room room size / minSize, descending TreeSet of ids
LC 1697 Checking Existence of Edge Length Limited Paths edge weight / query limit, ascending union-find
LC 2070 Most Beautiful Item for Each Query price, ascending prefix max over prices
LC 1146 Snapshot Array online, so binary search per key instead

Offline vs online is a real interview signal. “You are given all the queries in an array” is permission to reorder them; “implement a class with a query() method” is not. Say which one you are assuming out loud — it changes the achievable complexity.


Problems by Pattern

Pattern-Based Problem Tables

Custom Comparator Problems

Problem LC # Key Technique Difficulty
Largest Number 179 String comparison Medium
Custom Sort String 791 Character order Medium
Reorder Data in Log Files 937 Multi-key sort Easy
Two City Scheduling 1029 Cost difference Medium
Rank Teams by Votes 1366 Vote counting Medium
Sort Array by Parity 905 Even/odd separation Easy
Relative Sort Array 1122 Custom order Easy

Topological Sort Problems

Problem LC # Key Technique Difficulty
Course Schedule 207 Cycle detection Medium
Course Schedule II 210 Ordering with dependencies Medium
Alien Dictionary 269 Character ordering Hard
Minimum Height Trees 310 Tree centroid Medium
Parallel Courses 1136 Level-based processing Medium
Sequence Reconstruction 444 Unique ordering Medium

Interval Sorting Problems

Problem LC # Key Technique Difficulty
Merge Intervals 56 Sort and merge Medium
Insert Interval 57 Binary search insertion Medium
Meeting Rooms 252 Overlap check Easy
Meeting Rooms II 253 Sweep line Medium
Non-overlapping Intervals 435 Greedy removal Medium
Minimum Number of Arrows 452 Interval intersection Medium

K-th Element Problems

Problem LC # Key Technique Difficulty
Kth Largest Element 215 Quick select Medium
Top K Frequent Elements 347 Bucket sort Medium
Kth Smallest in Matrix 378 Binary search Medium
Find K Closest Elements 658 Two pointers Medium
K Closest Points to Origin 973 Quick select Medium
Kth Largest in Stream 703 Min heap Easy

Counting/Bucket Sort Problems

Problem LC # Key Technique Difficulty
Maximum Gap 164 Bucket sort Hard
H-Index 274 Counting sort Medium
Sort Characters By Frequency 451 Frequency buckets Medium
Relative Sort Array 1122 Counting sort Easy
Sort Array by Frequency 1636 Custom comparator Easy

Merge Sort Application Problems

Problem LC # Key Technique Difficulty
Merge k Sorted Lists 23 K-way merge Hard
Sort List 148 Linked list merge sort Medium
Count of Smaller Numbers 315 Merge sort with count Hard
Count of Range Sum 327 Merge sort Hard
Reverse Pairs 493 Modified merge sort Hard

Greedy Pairing Problems

Problem LC # Key Technique Difficulty
Minimize Maximum Pair Sum 1877 Sort + two pointers (min+max pairs) Medium
Array Partition 561 Sort + pair adjacent elements Easy
Boats to Save People 881 Sort + greedy two pointers Medium
Divide Players Into Teams 2491 Sort + pair smallest with largest Medium

Sorting as a One-Line Preprocessing Step (no new template needed)

Problem LC # Sort/Count trick Difficulty
Group Anagrams 49 sorted(word) (or a 26-length count tuple) as the hash key Medium
Valid Anagram 242 sort both strings, or compare frequency maps Easy
Contains Duplicate 217 sort then check adjacent pairs (HashSet is better) Easy
Minimum Increment to Make Array Unique 945 sort, then push each value to max(v, prev+1) Medium
Least Number of Unique Integers after K Removals 1481 count freq, sort freqs ASC, remove the rarest first Medium

Pattern Selection Strategy

text
Problem Analysis Flowchart:

1. Need custom ordering rules?
   ├── YES → Custom Comparator
   │         ├── Multiple criteria → Tuple comparison
   │         └── Complex logic → Comparison function
   └── NO → Continue to 2

2. Dealing with dependencies?
   ├── YES → Topological Sort
   │         ├── Detect cycle → Kahn's algorithm
   │         └── Find ordering → DFS approach
   └── NO → Continue to 3

3. Working with intervals?
   ├── YES → Sort by start/end
   │         ├── Merge overlapping → Greedy merge
   │         └── Find conflicts → Sweep line
   └── NO → Continue to 4

4. Finding k-th element?
   ├── YES → Quick Select or Heap
   │         ├── One-time query → Quick select O(n)
   │         └── Multiple queries → Heap O(n log k)
   └── NO → Continue to 5

5. Limited value range?
   ├── YES → Counting/Bucket Sort
   │         ├── Integers → Counting sort
   │         └── With precision → Bucket sort
   └── NO → Use standard sorting

Summary & Quick Reference

Complexity Quick Reference

Algorithm Best Case Average Worst Case Space Stable
Quick Sort O(n log n) O(n log n) O(n²) O(log n) No
Merge Sort O(n log n) O(n log n) O(n log n) O(n) Yes
Heap Sort O(n log n) O(n log n) O(n log n) O(1) No
Tim Sort O(n) O(n log n) O(n log n) O(n) Yes
Counting Sort O(n+k) O(n+k) O(n+k) O(k) Yes
Radix Sort O(nk) O(nk) O(nk) O(n+k) Yes

Template Quick Reference

Template Pattern Key Code
Quick Sort Partition pivot; partition; recurse
Merge Sort Divide & merge mid; merge(left, right)
Custom Sort Comparator key=lambda x: criteria
Quick Select K-th element partition until k
Counting Sort Value as index count[val]++
Topological Dependencies in_degree; queue

Common Patterns & Tricks

Python Sorting Tricks

python
# Sort with multiple keys
items.sort(key=lambda x: (x[0], -x[1], x[2]))

# Sort by custom class
items.sort(key=lambda x: x.priority, reverse=True)

# Stable sort in multiple passes
items.sort(key=lambda x: x.secondary)  # First
items.sort(key=lambda x: x.primary)    # Then primary

# In-place vs new list
arr.sort()           # In-place
sorted_arr = sorted(arr)  # New list

Java Sorting Tricks

java
// Lambda comparator
Arrays.sort(arr, (a, b) -> a - b);

// Method reference
Arrays.sort(arr, Integer::compare);

// Comparator chaining
Arrays.sort(items, Comparator
    .comparing(Item::getPriority)
    .thenComparing(Item::getName));

// Reverse order
Arrays.sort(arr, Collections.reverseOrder());

Problem-Solving Steps

  1. Identify Sorting Need

    • Is sorting necessary?
    • Can we use partial sorting?
    • Do we need stability?
  2. Choose Algorithm

    • Dataset size
    • Value range
    • Memory constraints
    • Stability requirement
  3. Define Comparison

    • Single or multiple keys?
    • Ascending or descending?
    • Special cases handling
  4. Optimize if Needed

    • Quick select for k-th element
    • Counting sort for limited range
    • Bucket sort for uniform distribution

Common Mistakes & Tips

🚫 Common Mistakes:

  • Modifying array during custom comparison
  • Integer overflow in comparator (a - b)
  • Not handling equal elements in comparator
  • Using unstable sort when stability needed
  • O(n²) algorithms for large datasets

✅ Best Practices:

  • Use built-in sort for most cases
  • Prefer Integer.compare() over subtraction
  • Test with duplicates and edge cases
  • Consider partial sorting for k elements
  • Use stable sort for equal element ordering

Interview Tips

  1. Algorithm Choice

    • Start with built-in sort
    • Optimize only if needed
    • Explain time/space trade-offs
  2. Custom Comparator

    • Handle all comparison cases
    • Avoid integer overflow
    • Maintain transitivity
  3. Common Questions

    • “Why Quick Sort over Merge Sort?”
    • “How to make Quick Sort stable?”
    • “When to use Counting Sort?”
  4. Follow-up Optimizations

    • Sort only k elements
    • External sorting for large data
    • Parallel sorting

Advanced Techniques

Hybrid Sorting

  • Tim Sort: Merge + Insertion
  • Intro Sort: Quick + Heap + Insertion
  • Used in Python and Java standard libraries

External Sorting

  • K-way merge for disk-based data
  • Used in databases and big data

Parallel Sorting

  • Divide data among processors
  • Parallel merge or sample sort
  • Heap: Priority queue, k-th element
  • Binary Search: On sorted arrays
  • Divide & Conquer: Merge sort pattern
  • Greedy: Interval scheduling
  • Graph: Topological ordering

LC Example

2-1) Pancake Sorting — LC 969

python
# python
# LC 969 Pancake Sorting
# V0
# IDEA : pankcake sort + while loop
# IDEA : 3 STEPS
#   -> step 1) Find the maximum number in arr
#   -> step 2) Reverse from 0 to max_idx
#   -> step 3) Reverse whole list
# https://github.com/yennanliu/CS_basics/blob/master/algorithm/python/pancake_sort.py
class Solution(object):
    def pancakeSort(self, arr):
        """Sort Array with Pancake Sort.
        :param arr: Collection containing comparable items
        :return: Collection ordered in ascending order of items
        Examples:
        >>> pancake_sort([0, 5, 3, 2, 2])
        [0, 2, 2, 3, 5]
        >>> pancake_sort([])
        []
        >>> pancake_sort([-2, -5, -45])
        [-45, -5, -2]
        """
        cur = len(arr)
        res = []
        while cur > 1:
            # step 1) Find the maximum number in arr
            max_idx = arr.index(max(arr[0:cur]))
            res = res + [max_idx+1, cur] # idx is 1 based
            # step 2) Reverse from 0 to max_idx
            # NOTE: `arr[:max_idx][::-1]` EXCLUDES arr[max_idx]; the commented
            #       `arr[max_idx::-1]` INCLUDES it — they are NOT equivalent.
            #       Prefer the commented form to keep the pivot element.
            #arr = arr[max_idx::-1] + arr[max_idx + 1 : len(arr)] # includes pivot
            arr = arr[:max_idx][::-1] + arr[max_idx + 1 : len(arr)]
            # step 3) Reverse whole list
            #arr = arr[cur - 1 :: -1] + arr[cur : len(arr)] # this is OK as well
            #arr = arr[:cur - 1][::-1] + arr[cur : len(arr)] # this is OK as well
            tmp = arr[::-1]
            arr = tmp
            cur -= 1
        print ("arr = " + str(arr))
        return res

# V1
# https://leetcode.com/problems/pancake-sorting/discuss/817978/Python-O(n2)-by-simulation-w-Comment
# https://leetcode.com/problems/pancake-sorting/discuss/330990/Python
class Solution:
    def pancakeSort(self, A):

        res = []

        for x in range(len(A), 1, -1):
            # Carry out pancake-sort from largest number n to smallest number 1

            # find the index of x
            i = A.index(x)

            # flip first i+1 elements to put x on A[0]
            # flip first x elements to put x on A[x-1]
            # now, x is on its corresponding position A[x-1] on ascending order
            # 
            """
            # array extend
            In [10]: x = [1,2,3]

            In [11]: x.extend([4])

            In [12]: x
            Out[12]: [1, 2, 3, 4]

            In [13]: x = [1,2,3]

            In [14]: x = x + [4]

            In [15]: x
            Out[15]: [1, 2, 3, 4]

            """
            #res.extend([i + 1, x])
            res = res + [i + 1, x]

            # update A
            """
            https://stackoverflow.com/questions/509211/understanding-slice-notation

            a[::-1]    # all items in the array, reversed
            a[1::-1]   # the first two items, reversed
            a[:-3:-1]  # the last two items, reversed
            a[-3::-1]  # everything except the last two items, reversed

            -> A[:i:-1] : last i items, reversed

            """
            A = A[:i:-1] + A[:i]
        #print ("res = " + str(res))
        return res

# V1
# IDEA : RECURSIVE
# https://leetcode.com/problems/pancake-sorting/discuss/553116/My-python-solution
# https://leetcode.com/problems/pancake-sorting/discuss/274921/PythonDetailed-Explanation-for-This-Problem
class Solution:
    def pancakeSort(self, A):
        pointer = len(A)
        result = []

        while pointer > 1:
            idx = A.index(pointer)
            result.append(idx + 1)
            A = A[idx::-1] + A[idx + 1:]
            result.append(pointer)
            A = A[pointer - 1::-1] + A[pointer:]
            pointer -= 1
            
        return result
java
// java
// aAlgorithm book (labu) p. 347

// record reverse op array
LinkedList<Integer> res = new LinkedList<>();

List<Integer> pancakeSort(int[] cakes){
	sort(cakes, cakes.length);
	return res;
}

// order first N pancakes
void sort(int[] cakes, int n){
	// base case
	if (n == 1) return;

	// find max index
	int maxCake = 0;
	int maxCakeIndex = 0;
	for (int i = 0; i < n; i ++){
		if (cakes[i] > maxCake){
			maxCakeIndex = i;
			maxCake = cakes[i];
		}
	}
	// after 1st flip, put max pancake to the 1st layer
	reverse(cakes, 0, maxCakeIndex);
	// record this flip
	res.add(maxCakeIndex+1);
	// 2nd flip, make max pancake to the bottom (last layer)
	reverse(cakes, 0, n-1);
	// record this flop
	res.add(n);
	// recursive call : flip the remaining pancakes
	sort(cakes, n-1);
}

/** flip arr[i..j] elements */
void reverse(int[] arr, int i, int j){
	while (i < j){
		int tmp = arr[i];
		arr[i] = arr[j];
		arr[j] = tmp;
		i++;
		j--;
	}
}

2-2) Reorder Data in Log Files — LC 937

python
# LC 937. Reorder Data in Log Files
# V0
# IDEA : SORT BY KEY
class Solution:
    def reorderLogFiles(self, logs):
        def f(log):
            id_, rest = log.split(" ", 1)
            """
            NOTE !!!
              2 cases:
               1) case 1: rest[0].isalpha() => sort by rest, id_
               2) case 2: rest[0] is digit =>  DO NOTHING (keep original order)

               syntax:
                 if condition:
                    return key1, key2, key3 ....
            """
            if rest[0].isalpha():
                return 0, rest, id_
            else:
                return 1, None, None
                #return 100, None, None  # since we need to put Digit-logs behind of Letter-logs, so first key should be ANY DIGIT BIGGER THAN 0 

        logs.sort(key = lambda x : f(x))
        return logs

# V1
# IDEA : SORT BY keys
# https://leetcode.com/problems/reorder-data-in-log-files/solution/
class Solution:
    def reorderLogFiles(self, logs):

        def get_key(log):
            _id, rest = log.split(" ", maxsplit=1)
            """
            NOTE !!!
              2 cases:
               1) case 1: rest[0].isalpha() => sort by rest, id_
               2) case 2: rest[0] is digit =>  DO NOTHING (keep original order)
            """
            return (0, rest, _id) if rest[0].isalpha() else (1, )

        return sorted(logs, key=get_key)

2-3) Meeting Rooms — LC 252

python
# LC 252. Meeting Rooms
# V0
class Solution:
    def canAttendMeetings(self, intervals):
        """
        NOTE this
        """
        intervals.sort(key=lambda x: x[0])
        for i in range(1, len(intervals)):
            """
            NOTE this : 
                -> we compare ntervals[i][0] and ntervals[i-1][1]
            """
            if intervals[i][0] < intervals[i-1][1]:
                return False
        return True

2-4) Custom Sort String — LC 791

python
# LC 791. Custom Sort String
# V0
# IDEA : COUNTER
from collections import Counter
class Solution(object):
    def customSortString(self, order, s):
        s_map = Counter(s)
        res = ""
        for o in order:
            if o in s_map:
                res += (o * s_map[o])
                del s_map[o]
        for s in s_map:
            res += s * s_map[s]
        return res

2-5) Find K Closest Elements — LC 658

python
# LC 658. Find K Closest Elements
# NOTE : there is also stack, binary search.. approaches
# V0'
# IDEA : SORTING
class Solution:
    def findClosestElements(self, arr, k, x):
        # Sort using custom comparator
        sorted_arr = sorted(arr, key = lambda num: abs(x - num))

        # Only take k elements
        result = []
        for i in range(k):
            result.append(sorted_arr[i])
        
        # Sort again to have output in ascending order
        return sorted(result)

2-6) Largest Number — LC 179

python
# LC 179. Largest Number
# V0
# IDEA : Sorting via Custom Comparator
class compare(str):
    # __lt__ defines ">" operator in python
    def __lt__(x, y):
        return x+y > y+x

class Solution:
    def largestNumber(self, nums):
        largest = sorted([str(v) for v in nums], key=compare) 
        largest = ''.join(largest) 
        return '0' if largest[0] == '0' else largest 

2-7) Permutation in String — LC 567

python
# LC 567 
# V0
# IDEA : collections + sliding window
from collections import Counter
class Solution(object):
    def checkInclusion(self, s1, s2):
        if len(s1) > len(s2):
            return False   
        l = 0
        tmp = ""
        _s1 = Counter(s1)
        _s2 = Counter()     
        for i, item in enumerate(s2):
            ### NOTE : we need to append new element first, then compare
            _s2[item] += 1
            tmp = s2[l:i+1]
            if _s2 == _s1 and len(tmp) > 0:
                return True
            if len(tmp) >= len(s1):
                _s2[tmp[0]] -= 1
                if _s2[tmp[0]] == 0:
                    del _s2[tmp[0]]
                l += 1
        return False
java
// java
// LC 567
// V2
// IDEA : SORTING
// https://leetcode.com/problems/permutation-in-string/editorial/
public boolean checkInclusion_3(String s1, String s2) {
    s1 = sort(s1);
    for (int i = 0; i <= s2.length() - s1.length(); i++) {
        if (s1.equals(sort(s2.substring(i, i + s1.length()))))
            return true;
    }
    return false;
}

public String sort(String s) {
    char[] t = s.toCharArray();
    Arrays.sort(t);
    return new String(t);
} 

2-8) Car Fleet — LC 853

java
// java

// LC 853. Car Fleet

    // V0
    // IDEA: pair position and speed, sorting (gpt)
    /**
     * IDEA :
     *
     * The approach involves sorting the cars by their starting positions
     * (from farthest to nearest to the target)
     * and computing their time to reach the target.
     * We then iterate through these times to count the number of distinct fleets.
     *
     *
     *
     * Steps in the Code:
     *  1.  Pair Cars with Their Speeds:
     *      •   Combine position and speed into a 2D array cars for easier sorting and access.
     *  2.  Sort Cars by Position Descending:
     *      •   Use Arrays.sort with a custom comparator to sort cars from farthest to nearest relative to the target.
     *  3.  Calculate Arrival Times:
     *      •   Compute the time each car takes to reach the target using the formula:
     *
     *  time = (target - position) / speed
     *
     *  4.  Count Fleets:
     *      •   Iterate through the times array:
     *      •   If the current car’s arrival time is greater than the lastTime (time of the last fleet), it forms a new fleet.
     *      •   Update lastTime to the current car’s time.
     *  5.  Return Fleet Count:
     *      •   The number of distinct times that exceed lastTime corresponds to the number of fleets.
     *
     */
    public int carFleet(int target, int[] position, int[] speed) {
        int n = position.length;
        // Pair positions with speeds and `sort by position in descending order`
        // cars : [position][speed]
        int[][] cars = new int[n][2];
        for (int i = 0; i < n; i++) {
            cars[i][0] = position[i];
            cars[i][1] = speed[i];
        }

        /**
         * NOTE !!!
         *
         *  Sort by position descending (simulate the "car arriving" process
         */
        Arrays.sort(cars, (a, b) -> b[0] - a[0]); // Sort by position descending

        // Calculate arrival times
        double[] times = new double[n];
        for (int i = 0; i < n; i++) {
            times[i] = (double) (target - cars[i][0]) / cars[i][1];
        }

        // Count fleets
        int fleets = 0;
        double lastTime = 0;
        for (double time : times) {
            /**
             *  4.  Count Fleets:
             *  •   Iterate through the times array:
             *  •   If the current car’s arrival time is greater than the lastTime (time of the last fleet), it forms a new fleet.
             *  •   Update lastTime to the current car’s time.
             */
            // If current car's time is greater than the last fleet's time, it forms a new fleet
            if (time > lastTime) {
                fleets++;
                lastTime = time;
            }
        }

        return fleets;
    }

2-9) Minimize Maximum Pair Sum in Array — LC 1877

java
// java
// LC 1877. Minimize Maximum Pair Sum in Array
// Pattern: Greedy Pairing — Sort + Two Pointers

// Core idea:
//   1. Sort the array
//   2. Pair smallest with largest (two pointers from both ends)
//   3. Track the maximum pair sum across all pairs
//
// Why it works: pairing large+large inflates the max unnecessarily;
// pairing min+max balances every pair sum and minimizes the maximum.

public int minPairSum(int[] nums) {
    Arrays.sort(nums);
    int left = 0, right = nums.length - 1;
    int ans = 0;
    while (left < right) {
        ans = Math.max(ans, nums[left] + nums[right]);
        left++;
        right--;
    }
    return ans;
}

Similar problems using the same Greedy Pairing pattern:

Problem LC # Twist
Array Partition 561 Maximize sum of pair minimums → pair adjacent after sort
Boats to Save People 881 Minimize boats → greedy two pointers with weight limit
Divide Players Into Teams 2491 Equal skill sum → pair 1st with last

2-10) TopK Frequent Words — LC 692

java
// java
// LC 692

// V0-1
// IDEA: Sort on map key set
public List<String> topKFrequent_0_1(String[] words, int k) {

    // IDEA: map sorting
    HashMap<String, Integer> freq = new HashMap<>();
    for (int i = 0; i < words.length; i++) {
        freq.put(words[i], freq.getOrDefault(words[i], 0) + 1);
    }
    List<String> res = new ArrayList(freq.keySet());

    /**
     * NOTE !!!
     *
     *  we directly sort over map's keySet
     *  (with the data val, key that read from map)
     *
     *
     *  example:
     *
     *          Collections.sort(res,
     *                 (w1, w2) -> freq.get(w1).equals(freq.get(w2)) ? w1.compareTo(w2) : freq.get(w2) - freq.get(w1));
     */
    Collections.sort(res, (x, y) -> {
        int valDiff = freq.get(y) - freq.get(x); // sort on `value` bigger number first (decreasing order)
        if (valDiff == 0){
            // Sort on `key ` with `lexicographically` order (increasing order)
            //return y.length() - x.length(); // ?
            return x.compareTo(y);
        }
        return valDiff;
    });

    // get top K result
    return res.subList(0, k);
}