Order statistics
Many questions ask about the position of a value in sorted order, not the whole sorted array: the minimum, the median, the 90th percentile, the k-th largest. This lesson names those questions and gives the simple solutions that the faster algorithms in the rest of the chapter improve on.
Learning goals
- Define the i-th order statistic, rank and select
- Find the minimum and maximum together with fewer comparisons
- Compute medians and percentiles, including even-sized inputs
- Choose between sorting, heaps and selection algorithms for a given query
Prerequisites
To know whether a student is in the top 10%, you do not need the whole class sorted. You need to know one value in sorted order and compare against it.
Minimum/maximum
The minimum or maximum is found with n - 1 comparisons, and no algorithm can do better: every other element must lose at least one comparison, and each comparison can eliminate only one candidate.
To find both together, compare elements in pairs first. The larger of each pair is a candidate for the maximum and the smaller for the minimum. Each pair costs 1 comparison, then 1 against the running maximum and 1 against the running minimum: 3 comparisons per 2 elements, ⌈3n/2⌉ - 2 in total, instead of 2n - 2.
1static int[] minMax(int[] a) {2int min, max, i;3if (a.length % 2 == 0) { // start with the first pair4if (a[0] < a[1]) { min = a[0]; max = a[1]; } else { min = a[1]; max = a[0]; }5i = 2;6} else { min = max = a[0]; i = 1; }7for (; i + 1 < a.length; i += 2) {8int small, large;9if (a[i] < a[i + 1]) { small = a[i]; large = a[i + 1]; } // 1 comparison for the pair10else { small = a[i + 1]; large = a[i]; }11if (small < min) min = small; // 1 comparison12if (large > max) max = large; // 1 comparison13}14return new int[] { min, max };15}
Worst
n - 1 comparisons for one; ⌈3n/2⌉ - 2 for both
Space
O(1)
Median
The median is the middle value in sorted order. For odd n it is the element at sorted index (n - 1) / 2 (0-based). For even n there are two middle elements: the lower median at n/2 - 1, the upper median at n/2, and many definitions take their average.
Sorting finds it in O(n log n). Quickselect finds it in expected O(n), and the median-of-medians algorithm in worst-case O(n). Two heaps maintain it as values arrive (lesson 19.04).
| Values | Sorted | Median |
|---|---|---|
| 7, 1, 5 | 1, 5, 7 | 5 |
| 7, 1, 5, 3 | 1, 3, 5, 7 | (3 + 5) / 2 = 4 |
| 9 | 9 | 9 |
Kth smallest/largest
The k-th smallest (1-based) is at sorted index k - 1. The k-th largest is the (n - k + 1)-th smallest, at sorted index n - k. Always state whether k is 1-based and whether duplicates count separately: in [5, 5, 3] the second largest is 5 if duplicates count and 3 if only distinct values count.
There are three standard solutions, compared below. Sorting is simplest. A heap of size k is best when k is small or when data streams in. Quickselect is fastest on average when the whole array is in memory.
| Method | Time | Space | Notes |
|---|---|---|---|
| Sort and index | O(n log n) | O(1) or O(n) | Simplest; leaves the array sorted |
| Min-heap of size k | O(n log k) | O(k) | Works on streams; no need to hold all data |
| Quickselect | O(n) expected, O(n²) worst | O(1) | Reorders the array |
| Median of medians | O(n) worst | O(1) or O(n) | Large constant; mostly theoretical |
Percentiles
The p-th percentile is the value below which p percent of the data falls. With the nearest-rank method it is the element at rank ⌈p / 100 · n⌉ (1-based): for n = 20 values, the 90th percentile is the 18th smallest.
Other definitions interpolate between neighbouring values, which is what spreadsheets and libraries such as NumPy do by default. State which method you use, because results differ on small samples. Performance monitoring often reports p50, p95 and p99 latency; computing them needs one selection per percentile, not a full sort per query.
1static int percentile(int[] sortedAscending, double p) { // nearest-rank method, 0 < p <= 1002int rank = (int) Math.ceil(p / 100.0 * sortedAscending.length); // 1-based3return sortedAscending[Math.max(rank, 1) - 1];4}56// 20 latencies, p95 -> rank = ceil(0.95 * 20) = 19 -> the 19th smallest
Rank and select
Rank and select are inverse operations. rank(x) is the number of elements smaller than x (or the 1-based position x would have in sorted order). select(i) returns the element of rank i.
On a static array, rank is a binary search on sorted data (lower bound) and select is an array lookup. When elements are inserted and deleted, you need a structure that supports both quickly: an order-statistic tree, where each node stores its subtree size, or a Fenwick tree over compressed values, both giving O(log n) for rank, select, insert and delete.
1// static data: rank is lowerBound, select is an index2int rank(int[] sorted, int x) { return lowerBound(sorted, x); } // elements < x3int select(int[] sorted, int i) { return sorted[i]; } // 0-based
Practice ladder
Now try it yourself
Easy
- Find the second largest value in one pass
- Find the minimum and maximum with 3n/2 comparisons
Medium
- Median of an array using sorting, then using quickselect
- Kth largest element in an array
- Compute the p95 of a latency list
Advanced
- Find the median of two sorted arrays in O(log(min(m, n)))
- Prove min and max together need ⌈3n/2⌉ - 2 comparisons
- Maintain rank queries under inserts with a Fenwick tree
Key takeaways
- Order statistics ask for a value by its sorted position; you rarely need a full sort for them.
- Min and max together take ⌈3n/2⌉ - 2 comparisons; median is the middle (or average of the two middles).
- Pick sort, a size-k heap or quickselect by data size, whether it streams, and how big k is.