Topics › Binary Search › BS on 2D Arrays
Matrix Median
Java source ↗ Edit this article ↗
Jump to code:
Problem
You are given an n × m integer matrix in which every row is sorted in non-decreasing order (columns carry no guarantee), and n × m is odd. Return the median of all n × m values: the value that would sit in the middle if every cell were poured into one list and sorted.
Constraints: 1 ≤ n, m ≤ 10^3, n × m is odd, values fit in a 32-bit signed integer.
Example 1
mat = [[1, 3, 5],
[2, 6, 9],
[3, 6, 9]]
answer = 5 // sorted: 1 2 3 3 5 6 6 9 9, the middle (5th) value is 5
Example 2
mat = [[1, 3, 8],
[2, 3, 4],
[1, 2, 5]]
answer = 3 // sorted: 1 1 2 2 3 3 4 5 8
Example 3
mat = [[1, 2, 3, 4, 5]]
answer = 3 // a single sorted row: its middle element
With total = n × m cells, the median is the element at index total / 2 of the sorted list (0-indexed). A useful way to restate that: the median is the smallest value x such that more than total / 2 cells are <= x. Both binary search approaches below are built on that restatement.
Approach 1: Brute force, flatten and sort
Intuition. Copy all cells into one array, sort it, read the middle. This works for any matrix and does not use the sorted rows at all.
Algorithm.
- Copy every cell into an array of length
n × m. - Sort it.
- Return the element at index
(n × m) / 2.
static int bruteForce(int[][] mat) {
int n = mat.length, m = mat[0].length;
int[] all = new int[n * m];
int k = 0;
for (int[] row : mat) for (int x : row) all[k++] = x;
Arrays.sort(all);
return all[(n * m) / 2];
}
Complexity. Time O(n m log(n m)) for the sort. Space O(n m) for the copy.
For 10^6 cells this is a few tens of millions of operations and a megabyte-scale copy: workable, but the sorted rows let us avoid both the sort and the copy.
Approach 2: Better, binary search on the value with a linear count
Intuition. Turn the question around. Instead of “which element is the median?”, ask “given a value x, how many cells are <= x?” Call that count(x). As x grows, count(x) can only grow, so the predicate “count(x) > total / 2” is false for small x and true from the median onward. The median is the smallest x for which it is true, and binary search over the value range finds it.
The range to search is [smallest cell, largest cell]. Since rows are sorted, the smallest cell is the minimum of the first column and the largest is the maximum of the last column. In this approach count(x) is computed naively by scanning every cell; the point is to see the search structure before optimising the count.
Algorithm.
- Set
loto the minimum first element over all rows andhito the maximum last element. - Let
half = total / 2. - While
lo <= hi: computemidandcount(mid).- If
count(mid) <= half, too few cells are<= mid, so the median is larger:lo = mid + 1. - Otherwise
midis at least the median:hi = mid − 1.
- If
- Return
lo.
static int countLinear(int[][] mat, int x) {
int count = 0;
for (int[] row : mat) for (int v : row) if (v <= x) count++;
return count;
}
static int better(int[][] mat) {
int n = mat.length, m = mat[0].length;
int lo = Integer.MAX_VALUE, hi = Integer.MIN_VALUE;
for (int[] row : mat) { lo = Math.min(lo, row[0]); hi = Math.max(hi, row[m - 1]); }
int half = (n * m) / 2;
while (lo <= hi) {
int mid = (int) Math.floorDiv((long) lo + hi, 2); // long: lo + hi overflows int when the range is wide
if (countLinear(mat, mid) <= half) lo = mid + 1; // too few values <= mid: the median is larger
else hi = mid - 1; // mid already covers the middle position
}
return lo;
}
Complexity. Time O(n m log(range)), where range is the spread of values, at most 2^32, so about 32 full scans. Space O(1).
Why does the loop end at the median? Let M be the median. For any x < M, at most half cells are <= x (the cells at sorted positions 0 .. half − 1), so the test fails and lo moves above x. For x >= M, at least half + 1 cells are <= x, so the test passes and hi moves below x. The two pointers close in on M from both sides and lo finishes at exactly M, which is a real cell value because the search range contains it.
Approach 3: Optimal, binary search on the value with per-row upper bounds
Intuition. The only expensive part of Approach 2 is count(x). Each row is sorted, so “how many values in this row are <= x” is the upper bound of x: the index of the first element greater than x, found by binary search in O(log m). Summing the upper bounds over all rows gives the full count in O(n log m) instead of O(n m).
Algorithm.
- Same search range and loop as Approach 2.
- For each probe
mid, computecount = sum over rows of upperBound(row, mid). - Move
loorhiexactly as before; returnlo.
static int upperBound(int[] row, int x) {
int lo = 0, hi = row.length - 1, ans = row.length;
while (lo <= hi) {
int mid = lo + (hi - lo) / 2;
if (row[mid] > x) { ans = mid; hi = mid - 1; }
else lo = mid + 1;
}
return ans;
}
static int optimal(int[][] mat) {
int n = mat.length, m = mat[0].length;
int lo = Integer.MAX_VALUE, hi = Integer.MIN_VALUE;
for (int[] row : mat) { lo = Math.min(lo, row[0]); hi = Math.max(hi, row[m - 1]); }
int half = (n * m) / 2;
while (lo <= hi) {
int mid = (int) Math.floorDiv((long) lo + hi, 2);
int count = 0;
for (int[] row : mat) count += upperBound(row, mid);
if (count <= half) lo = mid + 1;
else hi = mid - 1;
}
return lo;
}
Dry run on Example 1. total = 9, half = 4, lo = min(1, 2, 3) = 1, hi = max(5, 9, 9) = 9.
| lo | hi | mid | upper bounds per row | count | count > 4? | action |
|---|---|---|---|---|---|---|
| 1 | 9 | 5 | 3, 1, 1 | 5 | yes | hi = 4 |
| 1 | 4 | 2 | 1, 1, 0 | 2 | no | lo = 3 |
| 3 | 4 | 3 | 2, 1, 1 | 4 | no | lo = 4 |
| 4 | 4 | 4 | 2, 1, 1 | 4 | no | lo = 5 |
lo = 5 > hi = 4, so the median is 5. Notice how mid = 4 is not a cell value, yet the search handles it fine: the count simply does not change between 3 and 4, and lo keeps moving up until it reaches the smallest value whose count exceeds half.
Complexity. Time O(n log m log(range)): about 32 probes, each doing n binary searches of log m steps. For n = m = 1000 that is roughly 32 × 1000 × 10 = 320,000 operations, against 10^6 × 20 for sorting. Space O(1).
Edge cases and pitfalls
- The midpoint can overflow.
lo + hioverflowsintwhen the values span most of the integer range, andlo + (hi − lo) / 2overflows too whenlois very negative andhivery positive. The code adds inlongand usesMath.floorDivso the midpoint is correct for negative ranges as well (plain/would round toward zero). The test with[MIN_VALUE, 0, MAX_VALUE]exercises exactly this. - Duplicates.
count(x)counts all cells<= x, including repeated values, so a matrix of all1s returns1. The condition must be<=on the count side (count <= halfmovesloup); using<would return the wrong element when the median value appears several times. - Probing non-existent values. Binary search on the value range visits numbers that are not in the matrix. That is harmless because the final
lois the smallest value passing the count test, and the smallest such value is always a cell (the median itself). - Even
n × m. The problem guarantees an odd count. If the guarantee were dropped, the same code returns the element at indextotal / 2, the upper of the two middle elements; averaging the two middles would need a second search for the smallestxwithcount(x) > total / 2 − 1. - Unsorted columns. Nothing here needs sorted columns. Only the rows must be sorted, for the
upperBoundcount and for choosingloandhifrom the first and last columns. - Search range. Taking
lo = 0orhi = 10^9as fixed constants would still work for non-negative inputs, but tying the range to actual cell values makes the method correct for negative numbers and slightly faster.
Related problems
- Upper Bound is the per-row count used in the optimal approach.
- Median of 2 sorted arrays finds a median across two sorted arrays with a different binary search, over a partition rather than over values.
- Kth element of 2 sorted arrays generalises that partition search to any rank.
- Koko eating bananas is a pure “binary search on the answer with a monotone count” problem, the same skeleton as Approaches 2 and 3.
- Search in 2D matrix - II is a searching rather than counting problem on a matrix with sorted rows.