Matrix Median
Rows of the matrix are sorted. Find the overall median without flattening (odd element count).
- 1 <= r, c <= 100
- r * c is odd
- 1 <= matrix[i][j] <= 10⁹
- Each row is sorted ascending
Intuition
Matrix median gives a matrix whose rows are each sorted and asks for the median of all elements, with an odd total count. Flattening and sorting is O(nm log(nm)) and ignores the structure entirely.
The reframing is to search for the value rather than a position. The median is the smallest value with strictly more than half the elements less than or equal to it. So if you could count elements ≤ some candidate x quickly, you could test any candidate.
Counting is easy precisely because the rows are sorted — each row answers "how many of mine are ≤ x?" with a binary search in O(log m). Across n rows that is O(n log m), far cheaper than touching every element.
And the count is monotone in x: raising the candidate can only increase it. That is exactly the structure binary search needs, so:
- Binary search the value range, from the smallest first-column entry to the largest last-column entry.
When the count is at most half, the median must be larger; otherwise it is this value or smaller. The loop converges on the first value whose count exceeds half.
A subtle point: that converged value is guaranteed to be present in the matrix, because the count only changes at values that actually occur. So the search naturally lands on a real element rather than a gap between two.
Same binary-search-the-answer idea, but the predicate counts rather than places. You search over values, and for each guess ask how many entries are ≤ it — cheap, because every row is sorted and answers in O(log m). Recognise the trick: when the whole structure is too big to merge, count against a guess instead.
Approach
Before reading on: price up what the direct approach costs here, then ask what property lets you throw away half the range after one comparison. Aim for O(n log m · log R) time and O(1) space.
Search over values, not positions
The median is the smallest value with more than half the elements at or below it. Binary searching the value range sidesteps ever ordering elements across rows, which is what flattening would require.
Set bounds from the columns
The smallest possible median is the minimum of the first column and the largest is the maximum of the last, since rows are sorted. Tight bounds keep the iteration count low — roughly log of the value range.
Count elements ≤ mid per row
Each sorted row answers with a binary search — upper_bound gives the count directly. Summing across rows costs O(n log m), which is the inner loop of the whole algorithm.
Compare the count against half
If the count is at most n·m / 2, too few elements are at or below mid, so the median is larger — move lo up. Otherwise hi = mid, keeping mid as a candidate rather than excluding it.
Trust that the result is a real element
The count only increases at values present in the matrix, so the converged value must be one of them. No final lookup is needed to map the answer back to an actual element.
Cost of the value search
The value range halves each iteration, giving O(log(range)) steps of O(n log m) counting — O(n log m log range) overall, with O(1) space. Compare with O(nm log(nm)) for flattening and sorting, which also costs O(nm) memory.
Solution & live demo
Common pitfalls
Merging all rows and sorting
flat = sorted(x for row in mat for x in row) return flat[(n * m) // 2]
count = sum(bisect_right(row, mid) for row in mat)
Correct, but O(nm log nm) time and O(nm) space when the rows are already sorted. Binary searching values costs O(n log m log(range)) and allocates nothing.
Using bisect_left for the count
count = sum(bisect_left(row, mid) for row in mat)
count = sum(bisect_right(row, mid) for row in mat)
You need the number of elements less than or equal to mid. bisect_left excludes elements equal to it, so every duplicate of the candidate goes uncounted and the search settles one value too low.
Terminating with lo <= hi and returning mid
while lo <= hi:
...
return midwhile lo < hi:
...
return loThe median must be an element that actually appears, and a mid satisfying the count condition need not be one. Converging with lo < hi and returning lo lands on a real matrix entry.
Edge cases
Median is the middle of that row — the search still lands there.
Counting ≤ handles ties naturally; convergence lands on the duplicated value.