LC 74 · Medium · Template A · index remap
Search a 2D Matrix
A grid whose rows are sorted and where each row starts above where the previous row ended.
Recognise it
Before any code, what in the question tells you this is the pattern?
A matrix where each row is sorted AND the first value of each row exceeds the last of the previous row. That second condition is the tell: the whole grid is one sorted array wearing a 2D costume.
The brute force
Always have this one ready. It is the honest starting point, and it is what the real solution improves on.
Scan every cell of the matrix and compare it to the target.
O(m * n) time and O(1) space.
A slightly better attempt is to binary search the rows to find the right one, then binary search inside it, giving O(log m + log n). That is already fine. But noticing that log m + log n equals log(m * n) tells you those two searches were always one search wearing a disguise, and writing it as one is shorter and has fewer places to make an off-by-one mistake.
The approach
The idea in plain language, before it becomes syntax.
Read the guarantee carefully. Every row is sorted, and the first value of a row is greater than the last value of the row above it. Put those two facts together and the matrix, read left to right and then top to bottom, is one long sorted list. The rows are just line breaks.
So pretend the line breaks are not there. Binary search the index range from 0 to rows * cols - 1 exactly as you would on a flat array. The only extra work is that you cannot index a matrix with a single number, so at the moment you need to read a value you convert: the row is the index divided by the column count, and the column is the remainder.
This is worth recognising as a technique in its own right. The array you search does not have to exist in memory. Here it is a view over a matrix, and in other problems it is a range of possible answers. What binary search actually needs is an ordered index space and a way to ask a question at a given index.
The algorithm
The same idea again, as steps you could follow with a pencil.
The solution
Now the code, and why each decision in it is the way it is.
def searchMatrix(matrix, target):
m, n = len(matrix), len(matrix[0])
lo, hi = 0, m * n - 1
while lo <= hi:
mid = (lo + hi) // 2
val = matrix[mid // n][mid % n] # flatten -> 2D
if val == target:
return True
elif val < target:
lo = mid + 1
else:
hi = mid - 1
return FalseThe division and modulo happen inside the loop, at the single point where a value is read. Converting anywhere else spreads the mapping across the function and gives you more chances to swap the operands.
Note that cols, not rows, is the divisor in both expressions. Using the wrong dimension still produces valid indices on a square matrix, so it will pass your mental test and fail on anything rectangular. Test with a matrix that is not square.
Everything else is the plain template from LC 704. That is the payoff of the remap: no new loop shape to get right.
Where people slip
- Only valid when rows chain together (row i+1 starts above row i ends). If not, this is a different problem. See the follow-up.
- Index mapping:
row = mid // n,col = mid % n. Mixing upn(cols) andm(rows) is the classic bug.
Your notes
What tripped you up here? Write it in your own words.