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
- 1Ask whether sorting the input unlocks a simpler pass (two pointers, sweeps, grouping).
- 2Check whether original positions must be preserved; sort indices or pairs if so.
- 3Choose the key or comparator, and whether stability matters.
- 4Use quickselect or a heap if only the k-th element or the top k is needed.
- 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 - 1Variants
- 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
| Operation | Cost |
|---|---|
| Comparison sort (merge, heap, built-in) | O(n log n) |
| Quicksort | O(n log n) expected, O(n²) worst |
| Quickselect | O(n) expected |
| Counting sort | O(n + range) |
| Merge sort extra memory | O(n) |
25 Sorting & Searching problems
Easiest first. Problems marked Free can be solved without Pro.
- 1Order the Ledger by MergingFreeEasy
- 2Tally-Sort Exam ScoresFreeEasy
- 3Evens First, Each AscendingFreeEasy
- 4Shortest Words FirstEasy
- 5Shelf Order From a ReferenceEasy
- 6Students Out of LineEasy
- 7Rarest Values FirstEasy
- 8Widest Triangle FenceEasy
- 9Quick-Sort a Batch With RepeatsMedium
- 10Depot on a Straight RoadMedium
- 11Biggest Number From Price TagsMedium
- 12Zigzag LineupMedium
- 13Count Out-of-Order PairsMedium
- 14Line Up by Height TagsMedium
- 15Rank Teams by BallotsMedium
- 16Citation Impact ScoreMedium
- 17Rank by Hailstone StepsMedium
- 18Only Neighbour Inversions?Medium
- 19Radix-Sort Serial NumbersMedium
- 20Sort Each DiagonalMedium
- 21Pairs More Than Double LaterHard
- 22Stretches With Total in a BandHard
- 23Close Readings, Close in TimeHard
- 24Adjacent Swaps to Match the RosterHard
- 25Biggest Multiple of Three From Digit TilesHard
Solve them in the browser, in Python or JavaScript.