2Brute2Best
🔥0

Search a 2D Matrix

Medium·LeetCode #74Basic Anchor
LeetCode
30-Second Mental ModelBinary Search (2D grid as a flat sorted array)
Complexity:O(m · n)→O(log(m · n))
Key Insight

Read row by row, the grid is one sorted array of length m · n. We do not need to build that array. Give each cell a number idx from 0 to m·n − 1 in reading order. Since every row has n cells, cell idx is at row idx / n, column idx % n. Now run the ordinary binary search on idx.

Core Validity Invariant

Read row by row, the grid is one sorted array of m · n numbers, and flat position idx is the cell matrix[idx / n][idx % n].

01

Problem

You are given a grid matrix with m rows and n columns. It has two promises:

  • Every row is sorted from left to right.
  • The first number of each row is bigger than the last number of the row above it.

Given a number target, return true if it appears anywhere in the grid, and false otherwise. Your solution must run in O(log(m · n)) time.

Input

A 2D integer array matrix (m rows, n columns) with the two promises above, and an integer target.

Output

true if target is in the matrix, otherwise false.

Example 1
Input: matrix = [[1,3,5,7], [10,11,16,20], [23,30,34,60]], target = 3
Output: true

3 is in the first row, at column 1.

Example 2
Input: matrix = [[1,3,5,7], [10,11,16,20], [23,30,34,60]], target = 13
Output: false

13 would have to sit between 11 and 16 in the middle row, but it is not there.

Constraints
  • • 1 ≤ m, n ≤ 100 (m rows, n columns)
  • • −10^4 ≤ matrix[i][j], target ≤ 10^4
  • • Each row is sorted in non-decreasing order.
  • • The first number of each row is greater than the last number of the previous row.
02

First, think yourself

Before opening any solution, try to answer these on paper. Use hints only when stuck — each hint is counted.

  1. 1.

    If I read the grid row by row, left to right, what does the sequence of numbers look like?

  2. 2.

    What does the second promise (each row starts bigger than the previous row ends) add on top of "each row is sorted"?

  3. 3.

    Can I tell which row the target must be in just by looking at the first number of each row?

  4. 4.

    If I number the cells 0, 1, 2, … in reading order, how do I get back the row and column of cell number k?

  5. 5.

    Is O(log m + log n) really different from O(log(m · n))?

03

Brute force approach

04

Better approach

05

Optimized approach

06

Why does this algorithm work?

07

Dry run

08

All approaches compared

ApproachIdeaTimeSpace
Brute ForceScan all m · n cells and compare each one with targetO(m · n)O(1)
BetterBinary search the rows by their first number, then binary search inside that rowO(log m + log n)O(1)
OptimalTreat the grid as one sorted array of m · n cells; map idx → (idx / n, idx % n)O(log(m · n))O(1)
09

Why does this belong to this pattern?

Clues in the problem statement
  • “each row sorted”Sorted data → binary search is possible.
  • “first of each row > last of previous row”The rows continue each other → the whole grid is ONE sorted array.
  • “O(log(m · n))”One binary search over all m · n cells.
Rows sorted+rows continue each other+grid = one sorted array of m · n→Binary Search (2D grid as a flat sorted array)

This is the anchor of the 2D and partition sub-pattern: it is the classic binary search in disguise. The only new skill is turning a flat index into (row, col). The next question, Search a 2D Matrix II, looks almost the same but drops the "rows continue each other" promise — and then flattening breaks.

10

Don't make this mistake

Dividing by the number of rows instead of columns
✗ int value = matrix[mid / m][mid % m];
✓ int value = matrix[mid / n][mid % n]; // n = matrix[0].size()

Each row holds n cells (the number of columns), so that is what you divide by. With a square grid both work, which hides the bug. Try the 3×4 example: flat index 5 is the value 11 at (1, 1). 5 / 4 = 1, 5 % 4 = 1 ✓. Using m = 3 gives (1, 2) = 16 ✗.

Using this trick on Search a 2D Matrix II

Pattern-confusion trap. In LC 240 each row and column is sorted, but a row does not start after the previous row ends. Example: [[1,4],[2,5]] read row by row is 1, 4, 2, 5 — not sorted. Binary search on that line gives wrong answers. Flattening only works when the second promise holds.

Picking the row with < instead of <=
✗ if (matrix[mid][0] < target) row = mid, lo = mid + 1;
✓ if (matrix[mid][0] <= target) row = mid, lo = mid + 1;

In the two-search approach, if target equals the first number of a row, < skips that row and you search the row above it, which can't contain target. Using <= keeps the row that starts with target.

Mixing loop styles
✗ while (lo < hi) { ... hi = mid - 1; ... }
✓ while (lo <= hi) { ... hi = mid - 1; ... }

With hi = mid − 1 the range lo == hi still holds one unchecked cell, so the loop must be lo <= hi. With lo < hi you never check that last cell; on a 1×1 matrix you'd return false without looking.

11

Want to implement this?

Write it yourself on LeetCode, then come back and compare your code with the solutions above.

Open coding workspace
How did it go?
Beyond The LeetCode Problem

How this concept connects to System Design & C++ Systems

Bridge DSA with production engineering

Top engineering interviews test whether you understand how algorithms like Binary Search translate into production architectures and low-level memory mechanics.