Pattern 19 of 20

Sorting & Searching

Sort first, or sort smartly: custom orders, counting sort, quickselect and merge steps.

When to use it

  • Order reveals the structure (neighbours, duplicates, gaps)
  • A custom comparison defines the answer
  • You need the k-th element without sorting everything

The idea

Sorting turns many questions into a single pass over neighbours. When values are bounded, counting sort beats comparison sorting, and quickselect finds one rank in linear time on average.

Typical cost O(n log n) for comparison sorts; O(n + k) for counting sort.

How it works

Sorting is often the first move rather than the whole answer. Once data is ordered, duplicates are adjacent, pairs can be found with two pointers, intervals line up, and binary search becomes possible. Paying O(n log n) up front frequently turns a quadratic problem into a linear pass.

Interviews still test sorting algorithms directly. Merge sort splits in half, sorts each half and merges, which guarantees O(n log n) and is stable; its merge step also counts inversions. Quicksort partitions around a pivot and is fast in practice but quadratic with bad pivots, which random pivots avoid.

Quickselect reuses the partition step to find the k-th smallest element without sorting everything: after one partition, only one side can contain the answer, so the expected cost is linear.

When values are small integers in a known range, comparison sorts are not the limit. Counting sort and bucket sort place items directly by value in O(n + range). Custom comparators handle the disguised cases, such as ordering numbers to form the largest concatenation.

How to attack it

  1. 1Ask whether sorting the input unlocks a simpler pass (two pointers, sweeps, grouping).
  2. 2Check whether original positions must be preserved; sort indices or pairs if so.
  3. 3Choose the key or comparator, and whether stability matters.
  4. 4Use quickselect or a heap if only the k-th element or the top k is needed.
  5. 5Use counting or bucket sort when values fall in a small range.

Template

A skeleton to adapt, not a solution to paste. Write it from memory a few times.

import random


def merge_sort(a):
    if len(a) <= 1:
        return a
    mid = len(a) // 2
    left, right = merge_sort(a[:mid]), merge_sort(a[mid:])
    out, i, j = [], 0, 0
    while i < len(left) and j < len(right):
        if left[i] <= right[j]:        # <= keeps the sort stable
            out.append(left[i]); i += 1
        else:
            out.append(right[j]); j += 1
    return out + left[i:] + right[j:]


def quickselect(a, k):
    # k-th smallest (0-based), expected O(n). Mutates a.
    lo, hi = 0, len(a) - 1
    while True:
        p = random.randint(lo, hi)
        a[p], a[hi] = a[hi], a[p]
        store = lo
        for i in range(lo, hi):        # Lomuto partition around a[hi]
            if a[i] < a[hi]:
                a[i], a[store] = a[store], a[i]
                store += 1
        a[store], a[hi] = a[hi], a[store]
        if store == k:
            return a[store]
        if store < k:
            lo = store + 1
        else:
            hi = store - 1

Variants

Sort as preprocessing
Sort once, then sweep, group or use two pointers. Often the whole trick in medium problems.
Merge sort with counting
Count inversions or smaller elements to the right during the merge step.
Quickselect
Find the k-th smallest or the median in expected O(n) by recursing into one side of a partition.
Counting and bucket sort
Index by value when the range is small, or by frequency for top-k frequent items.
Custom comparator
Order by a derived rule, such as comparing a + b with b + a to build the largest number from parts.

Pitfalls

  • Calling sort() on numbers in JavaScript without a comparator; it sorts them as strings.
  • A comparator that returns a boolean instead of a negative, zero or positive number.
  • Losing original indices that the answer needs after sorting.
  • Quicksort or quickselect with a fixed first-element pivot on already sorted input, which goes quadratic.
  • Assuming every built-in sort is stable when the problem depends on it.

What it costs

OperationCost
Comparison sort (merge, heap, built-in)O(n log n)
QuicksortO(n log n) expected, O(n²) worst
QuickselectO(n) expected
Counting sortO(n + range)
Merge sort extra memoryO(n)

25 Sorting & Searching problems

Easiest first. Problems marked Free can be solved without Pro.

  1. 1Order the Ledger by MergingFreeEasy
  2. 2Tally-Sort Exam ScoresFreeEasy
  3. 3Evens First, Each AscendingFreeEasy
  4. 4Shortest Words FirstEasy
  5. 5Shelf Order From a ReferenceEasy
  6. 6Students Out of LineEasy
  7. 7Rarest Values FirstEasy
  8. 8Widest Triangle FenceEasy
  9. 9Quick-Sort a Batch With RepeatsMedium
  10. 10Depot on a Straight RoadMedium
  11. 11Biggest Number From Price TagsMedium
  12. 12Zigzag LineupMedium
  13. 13Count Out-of-Order PairsMedium
  14. 14Line Up by Height TagsMedium
  15. 15Rank Teams by BallotsMedium
  16. 16Citation Impact ScoreMedium
  17. 17Rank by Hailstone StepsMedium
  18. 18Only Neighbour Inversions?Medium
  19. 19Radix-Sort Serial NumbersMedium
  20. 20Sort Each DiagonalMedium
  21. 21Pairs More Than Double LaterHard
  22. 22Stretches With Total in a BandHard
  23. 23Close Readings, Close in TimeHard
  24. 24Adjacent Swaps to Match the RosterHard
  25. 25Biggest Multiple of Three From Digit TilesHard

Solve them in the browser, in Python or JavaScript.