Topics › Binary Search › BS on 2D Arrays
Search in a 2D matrix
LeetCode ↗ Java source ↗ Edit this article ↗
Jump to code:
Problem
You are given an n × m integer matrix with two properties: every row is sorted in non-decreasing order, and the first element of each row is strictly greater than the last element of the previous row. In other words, reading the matrix row by row gives one sorted sequence. Given a target, return true if it appears in the matrix and false otherwise.
Constraints: 1 ≤ n, m ≤ 100, values and target fit in a 32-bit signed integer. Aim for O(log(n × m)) time.
Example 1
matrix = [[ 1, 3, 5, 7],
[10, 11, 16, 20],
[23, 30, 34, 60]], target = 3
answer = true // row 0, column 1
Example 2
same matrix, target = 13
answer = false // 13 would fall between 7 and 10, in the gap between rows 0 and 1
Example 3
matrix = [[1]], target = 2
answer = false
The second property is what distinguishes this problem from Search in 2D matrix - II. Here rows do not merely happen to be sorted; the rows are chained, so the whole matrix is one sorted list that has been folded into rows of length m.
Approach 1: Brute force, scan every cell
Intuition. Ignore the ordering and compare every cell with the target. It is correct for any matrix and serves as the baseline the other approaches are checked against.
Algorithm.
- For each row and each cell in it, return
trueon a match. - Return
falseif the loops finish.
static boolean bruteForce(int[][] matrix, int target) {
for (int[] row : matrix) {
for (int x : row) if (x == target) return true;
}
return false;
}
Complexity. Time O(n × m). Space O(1).
Approach 2: Better, locate the row then binary search it
Intuition. Because the rows are chained, the target can live in at most one row: the row whose first element is at most the target and whose last element is at least the target. Scan the rows to find it, then run an ordinary binary search inside that single row. If no row brackets the target, the target falls into a gap between rows (or outside the matrix entirely) and the answer is false.
Algorithm.
- For each row, test
row[0] <= target <= row[m − 1]. - For the first (and only) row that passes, binary search it and return the result.
- If no row passes, return
false.
static boolean inRow(int[] row, int target) {
int lo = 0, hi = row.length - 1;
while (lo <= hi) {
int mid = lo + (hi - lo) / 2;
if (row[mid] == target) return true;
if (row[mid] < target) lo = mid + 1; else hi = mid - 1;
}
return false;
}
static boolean better(int[][] matrix, int target) {
for (int[] row : matrix) {
if (row.length == 0) continue;
if (row[0] <= target && target <= row[row.length - 1]) return inRow(row, target);
}
return false;
}
Complexity. Time O(n + log m): a linear scan over the rows plus one binary search. Space O(1).
Returning directly from inside the loop is valid only because rows are chained: once a row brackets the target, no other row can contain it. The row scan could itself be replaced by a binary search over first elements, giving O(log n + log m), which is the same as the next approach; the flat-index trick below achieves that with a single loop and no row bookkeeping.
Approach 3: Optimal, binary search on the flat index
Intuition. Imagine unrolling the matrix into a single sorted array of length n × m. We never need to build that array, because any flat index idx maps back to a cell by integer division and remainder: row = idx / m, col = idx % m. So a standard binary search over idx in [0, n × m − 1], reading matrix[idx / m][idx % m] wherever it would read arr[idx], searches the whole matrix in O(log(n × m)) time.
Algorithm.
- If the matrix has no rows or no columns, return
false. - Set
lo = 0,hi = n × m − 1. - While
lo <= hi: computemidandvalue = matrix[mid / m][mid % m].- If
value == target, returntrue. - If
value < target, setlo = mid + 1; otherwisehi = mid − 1.
- If
- Return
false.
static boolean optimal(int[][] matrix, int target) {
int n = matrix.length;
if (n == 0 || matrix[0].length == 0) return false;
int m = matrix[0].length;
int lo = 0, hi = n * m - 1;
while (lo <= hi) {
int mid = lo + (hi - lo) / 2;
int value = matrix[mid / m][mid % m]; // flat index -> (row, column)
if (value == target) return true;
if (value < target) lo = mid + 1; else hi = mid - 1;
}
return false;
}
Dry run on Example 1, target = 3, with n = 3, m = 4, so flat indices run from 0 to 11:
| lo | hi | mid | cell (mid / 4, mid % 4) | value | action |
|---|---|---|---|---|---|
| 0 | 11 | 5 | (1, 1) | 11 | 11 > 3: hi = 4 |
| 0 | 4 | 2 | (0, 2) | 5 | 5 > 3: hi = 1 |
| 0 | 1 | 0 | (0, 0) | 1 | 1 < 3: lo = 1 |
| 1 | 1 | 1 | (0, 1) | 3 | match: return true |
Four probes instead of twelve cells.
Complexity. Time O(log(n × m)), which equals O(log n + log m). Space O(1).
Why is the mapping right? Row r occupies flat indices r × m through r × m + m − 1. For any idx in that range, idx / m is r (integer division drops the remainder) and idx % m is the offset within the row. Together with the chained-rows property, the flat sequence is sorted, so binary search is valid.
Edge cases and pitfalls
- Target in a gap between rows.
13in Example 1 lies between7and10. The flat search narrows to neighbouring indices and exits withlo > hi; the row-based approach finds no bracketing row. Both returnfalse. - First and last cells.
1and60in Example 1 are found; the search range[0, n × m − 1]is inclusive on both ends. - Single row or single column. With
n = 1,mid / mis always0; withm = 1,mid % mis always0. The same code handles both. - Empty matrix.
matrix[0].lengthmust not be read whenn == 0; the short-circuitn == 0 || matrix[0].length == 0guards against it. - Overflow of
n × m. Under the stated constraintsn × m ≤ 10^4, but for very large matrices uselongfor the flat index. - Using this on an unchained matrix. If only rows and columns are sorted but rows are not chained (Search in 2D matrix - II), the flat sequence is not sorted and Approach 3 is wrong. Check which guarantee you have before choosing the method.
- Negative numbers. Nothing changes; the test file includes a matrix of negatives.
Related problems
- Search in 2D matrix - II drops the chaining guarantee and needs a staircase walk instead.
- Search X in sorted array is the one-dimensional binary search this problem reduces to.
- Find row with maximum 1’s is another problem where sorted rows let binary search replace scanning.
- Find Peak Element - II applies binary search to a matrix with no sorted order at all.