GeeksforGeeks Medium

Matrix Median

Rows of the matrix are sorted. Find the overall median without flattening (odd element count).

Constraints
  • 1 <= r, c <= 100
  • r * c is odd
  • 1 <= matrix[i][j] <= 10⁹
  • Each row is sorted ascending
binary-searchmatrix
Open on GeeksforGeeks ↗
02

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.

How to spot this pattern

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.

03

Approach

Try it first

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.

1

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.

2

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.

3

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.

4

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.

5

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.

6

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.

04

Solution & live demo

▶1from bisect import bisect_right
▶2 
▶3def matrix_median(mat):
▶4 n, m = len(mat), len(mat[0])
▶5 lo = min(row[0] for row in mat)
▶6 hi = max(row[-1] for row in mat)
▶7 need = (n * m) // 2
▶8 while lo < hi:
▶9 mid = (lo + hi) // 2
▶10 count = sum(bisect_right(row, mid) for row in mat)
▶11 if count <= need:
▶12 lo = mid + 1
▶13 else:
▶14 hi = mid
▶15 return lo
05

Common pitfalls

Merging all rows and sorting

✗ Wrong
flat = sorted(x for row in mat for x in row)
return flat[(n * m) // 2]
✓ Right
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

✗ Wrong
count = sum(bisect_left(row, mid) for row in mat)
✓ Right
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

✗ Wrong
while lo <= hi:
    ...
    return mid
✓ Right
while lo < hi:
    ...
return lo

The 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.

06

Edge cases

Single row

Median is the middle of that row — the search still lands there.

Heavy duplicates

Counting ≤ handles ties naturally; convergence lands on the duplicated value.

07

Complexity

Time
O(n log m · log R)
Space
O(1)
R = value range; counting is n rows × log m.