Matrix · 3 of 10
Find median in a row wise sorted matrix
Find the middle number of a grid whose rows are sorted, by guessing a number and counting how many numbers are at most that guess.
- Simple way
- time O(m*n*log(m*n))
room O(m*n) - Clever way
- time O(m*log(n)*log(max-min))
room O(1)
The problem, simply
You get a grid with m rows and n columns. Each row is sorted from small to big. The columns are not sorted. The total count m * n is odd.
Give back the median: the number that would sit exactly in the middle if you poured all the numbers into one line and sorted it. With m * n numbers, that is the (m * n + 1) / 2th smallest.
A story to remember it
Picture it
Each row is sorted on its own, but the columns are not. Sorted all together the numbers are 1, 2, 3, 3, 5, 6, 6, 9, 9, and the 5th one, 5, is the median.
- answer
Examples
The usual grid
- In
[[1,3,5],[2,6,9],[3,6,9]]- Out
5
Sorted together: 1, 2, 3, 3, 5, 6, 6, 9, 9. The 5th number is 5.
Repeated numbers
- In
[[1,3,8],[2,3,4],[1,2,5]]- Out
3
Sorted together: 1, 1, 2, 2, 3, 3, 4, 5, 8. The 5th number is 3. Repeats count as separate numbers.
One column
- In
[[1],[2],[3]]- Out
2
Each row has one number. The middle of 1, 2, 3 is 2.
Negative numbers
- In
[[-5,-1,4]]- Out
-1
One row is already sorted, so the middle seat holds -1. The search range starts at -5.
One cell
- In
[[7]]- Out
7
The only number is the middle one.
Way 1: the simple way
Pour every number into one long line, sort it, and pick the middle.
- Copy all
m * nnumbers into an array. - Sort the array.
- Return the number at index
(m * n) / 2.
import java.util.*;
class Solution {
public int findMedian(int[][] matrix) {
int m = matrix.length, n = matrix[0].length;
int[] all = new int[m * n];
int k = 0;
for (int[] row : matrix)
for (int value : row) all[k++] = value; // pour everything into one line
Arrays.sort(all);
return all[(m * n) / 2]; // the middle seat
}
}How long it takes: sorting m * n numbers is O(m*n*log(m*n)). Extra room: a copy of the whole grid, O(m*n). It ignores that the rows were already sorted.
Way 2: the clever way
Do not search for where the median sits. Search for what value it has.
The median is the smallest number x such that at least need = (m * n + 1) / 2 numbers in the grid are x or less. If we guess some mid:
- If at least
neednumbers aremidor less, the median ismidor smaller. - If fewer than
neednumbers are, the median is bigger thanmid.
Counting is fast, because each row is sorted: find where the numbers bigger than mid start in each row with a halving search (an upper bound).
lo= the smallest first number of any row.hi= the biggest last number of any row.- While
lo < hi: letmidbe halfway. Count numbers<= midin all rows. - If the count is less than
need, setlo = mid + 1. Otherwise sethi = mid. - When
lo == hi, that is the median.
Guess a number, count how many are smaller or equal, then halve the guesses
01 / 08
The median is the 5th smallest of 9 numbers. It must be between the smallest first number, 1, and the biggest last number, 9. So lo = 1 and hi = 9.
- lo
- 1
- hi
- 9
- need
- 5
- looking now
- checking
- done
- answer
- ignored
class Solution {
public int findMedian(int[][] matrix) {
int m = matrix.length, n = matrix[0].length;
int lo = Integer.MAX_VALUE, hi = Integer.MIN_VALUE;
for (int[] row : matrix) {
lo = Math.min(lo, row[0]); // smallest possible answer
hi = Math.max(hi, row[n - 1]); // biggest possible answer
}
int need = (m * n + 1) / 2; // the median is the need-th smallest
while (lo < hi) {
int mid = lo + (hi - lo) / 2;
int count = 0;
for (int[] row : matrix) count += countAtMost(row, mid);
if (count < need) lo = mid + 1; // too few hands up: the median is bigger
else hi = mid; // enough hands up: the median is mid or smaller
}
return lo;
}
// how many numbers in a sorted row are <= x (the first index holding a number > x)
private int countAtMost(int[] row, int x) {
int left = 0, right = row.length;
while (left < right) {
int mid = left + (right - left) / 2;
if (row[mid] <= x) left = mid + 1;
else right = mid;
}
return left;
}
}How long it takes: the guesses halve the range from min to max, so there are about log(max - min) guesses, at most 32 for int. Each guess counts in m rows with an O(log n) search. Total O(m * log(n) * log(max - min)). Extra room: O(1).
Why is the answer always a real number from the grid? Because lo stops at the smallest value whose count reaches need. Any value that is not in the grid has the same count as the grid number just below it, so it cannot be the smallest such value. In the animation, 4 is not in the grid, and the search moves past it.
Watch out for
(lo + hi) / 2can overflow when the numbers are huge or very negative. Uselo + (hi - lo) / 2, orlong.- Negative numbers: the range must start at the real minimum, not at 0 or 1.
loandhimust come from the first and last columns. The top left and bottom right cells are not the min and max here, because columns are not sorted.- Off by one in
need. For 9 numbers the median is the 5th, soneed = (9 + 1) / 2 = 5.
What interviewers ask next
- What if
m * nis even? Find the(m*n)/2th and the(m*n)/2 + 1th smallest with the same search (change onlyneed) and average them. - The
kth smallest number in this grid? Same code withneed = k. - Can you use a heap instead? Put the first number of each row in a min heap, pop
needtimes, and after each pop push the next number from the same row. That isO(need * log m)time andO(m)room, which is slower when the grid is big. - What if the grid is also sorted by columns? Then you can count in
O(m + n)with a staircase walk from the bottom left, instead of a halving search in every row. - Why not binary search on positions? Because positions in the grid say nothing about rank when only rows are sorted. Values do: count of numbers
<= xonly grows asxgrows, and that is all binary search needs.
Remember this
Grown-up words: raising hands for "at most this height" is counting with an upper bound search, guessing and halving the range of values is binary search on the answer, and the middle seat is the median, the (m*n+1)/2th order statistic.