2387. Median of a Row Wise Sorted Matrix
Problem Description
You are given an m x n matrix grid containing an odd number of integers where each row is sorted in non-decreasing order. Your task is to find and return the median of all elements in the matrix.
The median is the middle value when all elements are arranged in sorted order. Since the matrix contains an odd number of elements, the median will be the element at position ⌈(m × n) / 2⌉ when all elements are sorted.
Key constraints:
- Each row in the matrix is already sorted in non-decreasing order
- The total number of elements (
m × n) is odd - You must solve this problem in less than
O(m × n)time complexity
The challenge is to find the median without actually sorting all elements of the matrix, which would take O(m × n × log(m × n)) time. Instead, you need to leverage the fact that each row is already sorted to achieve a more efficient solution.
For example, if you have a 3×3 matrix with 9 elements total, the median would be the 5th smallest element across the entire matrix.
Intuition
Since we need to find the median in less than O(m × n) time, we can't afford to merge or sort all elements. However, we can think about this problem differently: instead of finding the exact position of the median, we can search for the median value itself.
The key insight is that the median has a special property: it's the smallest number x such that at least ⌈(m × n) / 2⌉ elements in the matrix are less than or equal to x. In other words, if we count how many elements are ≤ some value, the median is the smallest value where this count reaches our target position.
This transforms our problem into a search problem: we need to find the minimum value that satisfies a certain condition. This naturally leads us to binary search on the answer space.
For any candidate value x, we can efficiently count how many elements in the matrix are ≤ x by utilizing the fact that each row is sorted. For each row, we can use binary search to find the rightmost position where elements are ≤ x. The sum of these positions across all rows gives us the total count.
The beauty of this approach is that:
- We perform binary search on the possible values (the outer binary search)
- For each candidate value, we count elements using binary search on each sorted row (the inner binary search)
- This gives us
O(m × log(n) × log(range))time complexity, which is better thanO(m × n)
The solution searches for the first value where the count of elements ≤ that value is at least target = ⌈(m × n) / 2⌉. This value must be the median because it's the point where we've seen exactly half (or just over half) of all elements.
Learn more about Binary Search patterns.
Solution Implementation
1from typing import List
2from bisect import bisect_right
3
4
5class Solution:
6 def matrixMedian(self, grid: List[List[int]]) -> int:
7 """
8 Find the median of a row-wise sorted matrix.
9
10 The algorithm uses binary search on the value range to find the median.
11 For each candidate value, it counts how many elements are less than or equal to it.
12 The median is the smallest value where at least (m*n+1)/2 elements are <= to it.
13 """
14
15 def count_elements_less_than_or_equal(target_value):
16 """Count elements in the matrix <= target_value."""
17 total_count = 0
18 for row in grid:
19 total_count += bisect_right(row, target_value)
20 return total_count
21
22 # Get matrix dimensions
23 num_rows = len(grid)
24 num_cols = len(grid[0])
25
26 # Calculate the position of the median element
27 median_position = (num_rows * num_cols + 1) // 2
28
29 # Binary search using the template to find smallest value with count >= median_position
30 left, right = 0, 10**6
31 first_true_index = -1
32
33 while left <= right:
34 mid = (left + right) // 2
35 if count_elements_less_than_or_equal(mid) >= median_position:
36 # Feasible - found a value where count >= target
37 first_true_index = mid
38 right = mid - 1 # Try to find smaller value
39 else:
40 left = mid + 1
41
42 return first_true_index
431class Solution {
2 private int[][] grid;
3
4 /**
5 * Find the median of all elements in a row-wise sorted matrix.
6 * Uses binary search on the value range to find the median.
7 */
8 public int matrixMedian(int[][] grid) {
9 this.grid = grid;
10 int rows = grid.length;
11 int cols = grid[0].length;
12
13 // Calculate the position of the median (1-indexed)
14 int medianPosition = (rows * cols + 1) >> 1;
15
16 // Binary search using the template to find smallest value with count >= medianPosition
17 int left = 0;
18 int right = 1000000;
19 int firstTrueIndex = -1;
20
21 while (left <= right) {
22 int mid = left + (right - left) / 2;
23
24 // Count how many elements are less than or equal to mid
25 if (count(mid) >= medianPosition) {
26 // Feasible - found a value where count >= target
27 firstTrueIndex = mid;
28 right = mid - 1; // Try to find smaller value
29 } else {
30 left = mid + 1;
31 }
32 }
33
34 return firstTrueIndex;
35 }
36
37 /**
38 * Count the number of elements in the matrix that are less than or equal to x.
39 * Uses binary search on each sorted row to efficiently count elements.
40 */
41 private int count(int x) {
42 int totalCount = 0;
43
44 // For each row in the matrix
45 for (int[] row : grid) {
46 int left = 0;
47 int right = row.length;
48
49 // Binary search to find the first position where row[position] > x
50 while (left < right) {
51 int mid = (left + right) >> 1;
52
53 if (row[mid] > x) {
54 right = mid;
55 } else {
56 left = mid + 1;
57 }
58 }
59
60 totalCount += left;
61 }
62
63 return totalCount;
64 }
65}
661class Solution {
2public:
3 int matrixMedian(vector<vector<int>>& grid) {
4 // Get matrix dimensions
5 int numRows = grid.size();
6 int numCols = grid[0].size();
7
8 // The median position in a sorted array of m*n elements
9 int medianPosition = (numRows * numCols + 1) / 2;
10
11 // Lambda function to count elements less than or equal to a given value
12 auto countSmallerOrEqual = [&](int value) {
13 int count = 0;
14 for (const auto& row : grid) {
15 count += (upper_bound(row.begin(), row.end(), value) - row.begin());
16 }
17 return count;
18 };
19
20 // Binary search using the template to find smallest value with count >= medianPosition
21 int left = 0;
22 int right = 1000000;
23 int firstTrueIndex = -1;
24
25 while (left <= right) {
26 int mid = left + (right - left) / 2;
27
28 if (countSmallerOrEqual(mid) >= medianPosition) {
29 // Feasible - found a value where count >= target
30 firstTrueIndex = mid;
31 right = mid - 1; // Try to find smaller value
32 } else {
33 left = mid + 1;
34 }
35 }
36
37 return firstTrueIndex;
38 }
39};
401function matrixMedian(grid: number[][]): number {
2 // Get matrix dimensions
3 const numRows: number = grid.length;
4 const numCols: number = grid[0].length;
5
6 // The median position in a sorted array of m*n elements
7 const medianPosition: number = Math.floor((numRows * numCols + 1) / 2);
8
9 // Helper function to find upper bound index (first element > target)
10 const upperBound = (arr: number[], target: number): number => {
11 let left: number = 0;
12 let right: number = arr.length;
13
14 while (left < right) {
15 const mid: number = Math.floor((left + right) / 2);
16 if (arr[mid] <= target) {
17 left = mid + 1;
18 } else {
19 right = mid;
20 }
21 }
22 return left;
23 };
24
25 // Helper function to count elements less than or equal to a given value
26 const countSmallerOrEqual = (value: number): number => {
27 let count: number = 0;
28 for (const row of grid) {
29 count += upperBound(row, value);
30 }
31 return count;
32 };
33
34 // Binary search using the template to find smallest value with count >= medianPosition
35 let left: number = 0;
36 let right: number = 1000000;
37 let firstTrueIndex: number = -1;
38
39 while (left <= right) {
40 const mid: number = Math.floor((left + right) / 2);
41
42 if (countSmallerOrEqual(mid) >= medianPosition) {
43 // Feasible - found a value where count >= target
44 firstTrueIndex = mid;
45 right = mid - 1; // Try to find smaller value
46 } else {
47 left = mid + 1;
48 }
49 }
50
51 return firstTrueIndex;
52}
53Solution Approach
The solution implements the two binary search approach mentioned in the reference. Let's break down the implementation step by step:
1. Define the counting function:
def count(x):
return sum(bisect_right(row, x) for row in grid)
This helper function counts how many elements in the entire matrix are less than or equal to x. For each row, bisect_right(row, x) returns the rightmost position where we can insert x to keep the row sorted, which effectively gives us the count of elements ≤ x in that row. We sum these counts across all rows.
2. Calculate the target position:
m, n = len(grid), len(grid[0])
target = (m * n + 1) >> 1
Since we have an odd number of elements, the median is at position ⌈(m × n) / 2⌉. The expression (m * n + 1) >> 1 is equivalent to (m * n + 1) // 2, which gives us the ceiling of (m × n) / 2 for odd numbers.
3. Perform binary search using the template:
We define our feasible function: feasible(x) = count(x) >= target. This creates a pattern like [false, false, ..., true, true, ...] as x increases. We want to find the first true value (the smallest x where count >= target).
Using the standard binary search template:
left, right = 0, 10**6 first_true_index = -1 while left <= right: mid = (left + right) // 2 if count(mid) >= target: # Feasible first_true_index = mid right = mid - 1 # Try to find smaller value else: left = mid + 1 return first_true_index
The template finds the smallest value x such that count(x) >= target, which is exactly our median value.
Time Complexity:
- Outer binary search:
O(log(range))where range is the value range (10^6 in this case) - For each binary search iteration, we call
count(x)which performsmbinary searches, each takingO(log n)time - Total:
O(m × log(n) × log(range))
This is significantly better than the naive O(m × n × log(m × n)) approach of merging and sorting all elements.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through finding the median of this 3×3 matrix:
grid = [[1, 1, 2], [2, 3, 3], [1, 3, 4]]
Step 1: Setup
- Matrix dimensions: m = 3, n = 3
- Total elements: 3 × 3 = 9 (odd number)
- Target position: (9 + 1) >> 1 = 5 (we need the 5th smallest element)
- Feasible function:
count(x) >= 5
Step 2: Binary Search using Template
Initialize: left = 0, right = 10^6, first_true_index = -1
(After many iterations narrowing down, we focus on the key iterations)
Key Iteration: left = 1, right = 4
mid = (1 + 4) // 2 = 2- Count elements ≤ 2:
- Row 1: [1, 1, 2] → bisect_right gives 3 (all elements ≤ 2)
- Row 2: [2, 3, 3] → bisect_right gives 1 (only first element ≤ 2)
- Row 3: [1, 3, 4] → bisect_right gives 1 (only first element ≤ 2)
- Total count = 3 + 1 + 1 = 5 >= 5 → feasible!
- Update
first_true_index = 2,right = mid - 1 = 1
Next Iteration: left = 1, right = 1
mid = (1 + 1) // 2 = 1- Count elements ≤ 1:
- Row 1: [1, 1, 2] → bisect_right gives 2
- Row 2: [2, 3, 3] → bisect_right gives 0
- Row 3: [1, 3, 4] → bisect_right gives 1
- Total count = 2 + 0 + 1 = 3 < 5 → not feasible
- Update
left = mid + 1 = 2
Final: left = 2, right = 1
left > right, loop terminates- Result:
first_true_index = 2
Step 3: Result
The binary search finds that 2 is the smallest value where at least 5 elements are ≤ to it. Therefore, the median is 2.
To verify: Sorting all elements gives [1, 1, 1, 2, 2, 3, 3, 3, 4], and the 5th element is indeed 2.
The key insight is that we never actually sorted all elements. Instead, we efficiently searched for the value that would be at the median position by counting how many elements are less than or equal to candidate values.
Time and Space Complexity
The time complexity is O(m × log n × log M), where m is the number of rows, n is the number of columns of the grid, and M is the maximum element in the grid (here M = 10^6).
Breaking down the complexity:
- The outer binary search using
bisect_lefton the range[0, 10^6]performsO(log M)iterations - For each iteration, the
countfunction is called - Inside
count, we iterate throughmrows - For each row,
bisect_rightperforms binary search on a sorted row of lengthn, takingO(log n)time - Therefore, each
countcall takesO(m × log n)time - Total time complexity:
O(log M × m × log n) = O(m × log n × log M)
The space complexity is O(1) as the algorithm only uses a constant amount of extra space for variables like m, n, and target. The bisect_left function with range(10^6 + 1) doesn't actually create the full range in memory due to Python's lazy evaluation of range objects.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Using Wrong Binary Search Template
The Pitfall: Using while left < right with right = mid instead of the standard template with while left <= right, first_true_index, and right = mid - 1.
Why It's Wrong: Different binary search variants behave differently at boundaries. Using inconsistent patterns makes code harder to reason about and more prone to off-by-one errors.
The Fix: Use the standard template consistently:
left, right = 0, 10**6 first_true_index = -1 while left <= right: mid = (left + right) // 2 if count(mid) >= target: first_true_index = mid right = mid - 1 else: left = mid + 1 return first_true_index
2. Incorrect Value Range Assumption
The code assumes all matrix values are between 0 and 10^6, which may not always be true. If the matrix contains negative numbers or values larger than 10^6, the binary search will fail to find the correct median.
Solution: Find the actual min and max values in the matrix:
min_val = min(row[0] for row in grid)
max_val = max(row[-1] for row in grid)
left, right = min_val, max_val
3. Confusion Between Element Count and Element Value
A common mistake is confusing what the binary search returns. We're searching through a range of values with a counting function. The result is the actual median value, not an index into the matrix.
4. Off-by-One Error in Median Position Calculation
When calculating the median position, using (m * n + 1) // 2 vs (m * n) // 2 can lead to different results. The current formula works for odd-sized matrices but would need adjustment for even sizes.
Solution for odd matrices (current problem):
median_position = (m * n + 1) // 2
How does quick sort divide the problem into subproblems?
Recommended Readings
https assets algo monster cover_photos Binary_Search svg Binary Search Intuition Binary search is an efficient array search algorithm It works by narrowing down the search range by half each time If you have looked up a word in a physical dictionary you've already used binary search in real life Let's
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion If you prefer videos here's a video that explains recursion in a fun and easy way Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask him
Want a Structured Path to Master System Design Too? Don’t Miss This!