DSA sheet · Binary Search · Search in a 2D matrix pattern

Search a Sorted Matrix

This video opens the last binary search pattern of the sheet: searching inside a 2D matrix (a grid of numbers with rows and columns). The teacher uses LeetCode 74, where the whole grid is sorted so strongly that you can run one binary search to pick the right row, and then a second binary search inside that row. The main lesson is reading the two properties of the matrix and asking "do these let me throw away half each time?". She also explains why we search the rows first and not the columns, and how that would flip if the matrix were arranged the other way.

Every part below follows the same order:
① the question in simple words → ② what the constraints tell us → ③ intuition → ④ building the conditions from examples → ⑤ approach steps → ⑥ code → ⑦ code line by line → ⑧ dry run → ⑨ complexity & remember

Part 0 · Before starting

Why binary search works at all

Binary search needs one thing: when you look at one position, you must be able to say for sure "the answer is not on this side". That's possible when the data is sorted (or, more generally, when some yes/no question is "no, no, no, … yes, yes, yes" in order). Then each look throws away half of what's left.

low, high, mid

index0123456
value1357101116target 11: mid = 3 → 7 < 11 → throw away the left half
after1357101116low = 4, high = 6 → mid = 5 → found ✓

Why mid = low + (high - low) // 2?

The simple form is (low + high) // 2. In Java or C++, if low and high are both close to the biggest int (about 2.1 × 10⁹), their sum overflows (goes past the biggest value and wraps around to a negative number), so mid becomes garbage. low + (high - low) // 2 never builds that big sum, so it's safe. The teacher says to choose between the two "depending on the constraints". In Python, ints never overflow, so both forms give the same answer. We still write the safe form so the habit carries over to other languages.

Matrix words used on this page

Part A · Binary search on rows, then on the row

LeetCode 74 · Search a 2D Matrix

1The question in simple words

You get an m × n matrix of integers and a number target. Return True if target is somewhere in the matrix, otherwise False. The matrix has two special properties:

  1. Every row is sorted from left to right (non-decreasing).
  2. The first number of each row is bigger than the last number of the row above it.
          col 0   1   2   3
  row 0  [  1    3   5   7 ]
  row 1  [ 10   11  16  20 ]      10 > 7   (row 1 starts after row 0 ends)
  row 2  [ 23   30  34  60 ]      23 > 20  (row 2 starts after row 1 ends)

Check property 1: 1 3 5 7 sorted ✓, 10 11 16 20 sorted ✓, 23 30 34 60 sorted ✓. Check property 2: 10 > 7 ✓ and 23 > 20 ✓.

The problem also says the solution must run in O(log(m × n)). That line is a big hint, and we'll come back to it.

2What the constraints tell us

3Intuition: pick the row first, then search inside it

Say the target is 3. Look at the first number of each row, the smallest number of that row:

So one comparison against the first number of a row lets us throw away that row and everything below it. That's a "throw away a whole side" move, which is exactly binary search. But which row should we compare with first? Don't go row by row. Use binary search over the rows: check the middle row, throw away half the rows, repeat.

The goal of this first search is to find the valid row: the one row where the target would have to be, if it's in the matrix at all. It's "would" because the target might still be missing. For example, target 4 must be in row 0 if anywhere (1 ≤ 4 ≤ 7), but row 0 has no 4, so the answer is False. Still, row 0 is the only row worth searching.

Once we know the valid row, it's just a sorted 1D array, so run a normal binary search on it.

4Building the conditions from examples

First search, over rows: top = 0, bottom = rows - 1, and mid is a row number, the middle row. We compare the target with the first value matrix[mid][0] and the last value matrix[mid][n - 1] of that row.

Example target = 3 → first value bigger than target → go up

top = 0, bottom = 2 → mid = row 1, which starts at 10. 10 > 3, so 3 isn't in row 1 or below. Keep only the rows above mid:

Condition: target < first value of mid rowbottom = mid - 1 (the answer is in the upper part)

Example target = 23 → is it "go down" or "this row"?

Mid row 1 starts at 10, and 10 < 23. The teacher's first idea was "first value smaller than target → move down". Then she stops: what if the target were 11? 10 < 11 too, but 11 is in row 1. So "first value is smaller" alone can't tell "this row" apart from "a lower row". We need to also look at the last value of the row:

So the teacher puts a new check before the others:

Condition 1 (checked first): target fits inside the mid rowif matrix[mid][0] <= target <= matrix[mid][n - 1]: save row = mid and break. We found the valid row, so there's no reason to keep moving the pointers.
Condition 3 (else): target is past the mid rowtop = mid + 1 (the answer is in the lower part)
Doubt 1: in the "go down" case, why mid + 1 and not mid? Couldn't the target still be in the mid row?
→ No. If the target were anywhere from the first to the last value of the mid row (10 to 20), Condition 1 would already have caught it. We only reach the last branch when the target is not inside that range and not smaller than the first value. So it must be bigger than the last value, and the mid row is safe to drop.
Doubt 2: what if no row is valid?
→ Take target 61. Every row says "61 isn't in me", even the last one (61 > 60). The pointers keep moving down until top passes bottom, and row stays at its starting value -1. Then we return False straight away. (In the video she says "return minus one" here, but the function returns True/False, so it's False.)

Second search, inside the valid row

Now it's a plain binary search on matrix[row]: left = 0, right = n - 1, mid is now a column number.

If this loop ends without finding it, the valid row simply doesn't contain the target (like target 4 in row 0) → return False.

Doubt 3: while explaining the code she says "while top less than bottom" and "while left less than right". Is < right?
→ It has to be <=. With <, her own example breaks: target 3 → mid row 1 → bottom becomes 0 → now top = 0 and bottom = 0, and 0 < 0 is false, so the loop stops before checking row 0. row stays −1 and we wrongly return False. The same thing happens in a 1-row matrix. When top == bottom there is still one row left to check, so the loop must run. Her code passed on LeetCode, so it's most likely just how she said it out loud. When you write it yourself, use <= in both loops.

5Approach steps

  1. m = len(matrix), n = len(matrix[0]).
  2. Binary search over rows with top = 0, bottom = m - 1, row = -1.
  3. For the mid row: if target is between its first and last value → row = mid, break. Else if target is smaller than its first value → bottom = mid - 1. Else → top = mid + 1.
  4. If row == -1 → return False.
  5. Binary search inside matrix[row] with left = 0, right = n - 1. Found → True.
  6. Loop ended → return False.

6Code (Python)

Search a 2D Matrix: rows first, then the row
class Solution:
    def searchMatrix(self, matrix, target):
        m = len(matrix)              # rows
        n = len(matrix[0])           # columns

        # ---- search 1: find the valid row ----
        top, bottom = 0, m - 1
        row = -1
        while top <= bottom:
            mid = top + (bottom - top) // 2
            if matrix[mid][0] <= target <= matrix[mid][n - 1]:
                row = mid            # target can only be in this row
                break
            elif target < matrix[mid][0]:
                bottom = mid - 1     # go up
            else:
                top = mid + 1        # go down

        if row == -1:                # no row can hold the target
            return False

        # ---- search 2: normal binary search inside that row ----
        left, right = 0, n - 1
        while left <= right:
            mid = left + (right - left) // 2
            if matrix[row][mid] == target:
                return True
            elif matrix[row][mid] < target:
                left = mid + 1       # go right
            else:
                right = mid - 1      # go left
        return False                 # valid row, but the value is missing

7Code line by line

linewhat it means
m = len(matrix) n = len(matrix[0])Count rows and columns. The constraints promise at least one of each, so matrix[0] exists.
top, bottom = 0, m - 1The rows still possible: all of them, at the start.
row = -1"No valid row found yet". −1 can never be a real row number, so it works as a flag.
while top <= bottom:Keep going while at least one row is left (<=, see Doubt 3).
mid = top + (bottom - top) // 2The middle row. The safe form from Part 0.
if matrix[mid][0] <= target <= matrix[mid][n - 1]:Is the target between the smallest and biggest value of this row? Python lets us chain the two comparisons. Checked first, on purpose.
row = mid breakRemember the row and stop the first search. Its only job was to find this row.
elif target < matrix[mid][0]: bottom = mid - 1Even the smallest value of this row is too big → drop this row and every row below.
else: top = mid + 1The only case left: the target is bigger than this row's last value → drop this row and every row above.
if row == -1: return FalseThe first search ran out of rows without a fit, e.g. target 61, or a target smaller than matrix[0][0].
left, right = 0, n - 1Now search the columns of that one row.
if matrix[row][mid] == target:Row is fixed (the valid row). Only the column (mid) changes in this loop.
elif matrix[row][mid] < target: left = mid + 1Too small → the target is further right.
else: right = mid - 1Too big → the target is further left.
return FalseThe row could have held it, but it isn't there (target 4).

8Dry run

Target 3 (the teacher's main example)

          col 0   1   2   3
  row 0  [  1   3   5   7 ]   ← valid row
  row 1  [ 10   11  16  20 ]   ← mid in round 1 (10 > 3, drop it and below)
  row 2  [ 23   30  34  60 ]
steptopbottommid rowfirst … lastdecisionwhat we throw away
102110 … 203 < 10 → bottom = 0rows 1 and 2
20001 … 71 ≤ 3 ≤ 7 → row = 0, break(nothing, we stop)

Now search inside row 0:

index0123
row 01357left 0, right 3 → mid = 0 + (3 − 0) // 2 = 1 → 3 == 3 → True

Target 23 (go down, then find)

steptopbottommid rowfirst … lastdecisionwhat we throw away
102110 … 2023 > 20 → top = 2rows 0 and 1
222223 … 60fits → row = 2-
index0123
step 123303460mid 1: 30 > 23 → right = 0
step 223303460mid 0: 23 == 23 → True

Target 4 (valid row exists, value doesn't)

Row search: mid row 1 (10 > 4) → bottom = 0 → row 0 (1 ≤ 4 ≤ 7) → row = 0. Column search:

index0123
step 11357mid 1: 3 < 4 → left = 2
step 21357mid 2: 5 > 4 → right = 1
endleft 2 > right 1 → loop ends → False

Target 61 (no valid row at all)

steptopbottommid rowfirst … lastdecision
102110 … 2061 > 20 → top = 2
222223 … 6061 > 60 → top = 3
end32top > bottom, row still −1 → False without a second search

9Complexity & remember

RememberSearch 1 finds the valid row: "fits between first and last" → stop · smaller than first → up · else → down. Search 2 is a normal binary search on that row. <= in both loops. Total log(m·n).

Part B · Why rows first? The "open it up" view

1The question in simple words

Same problem. The teacher raises an interview-style "why": why did we binary search the rows first and the columns second? Why not columns first?

2What the constraints tell us

Same as Part A. Nothing new. This part is about understanding, not about size.

3Intuition: lay the rows out in one line

Take the rows and put them one after another (we can call this opening up the matrix, row by row):

index01234567891011
value13571011162023303460fully sorted ✓

Because each row is sorted and each row starts after the previous one ends, this line is one big sorted array of m × n = 3 × 4 = 12 numbers. Binary search on N items takes log N steps, so here it's log(m × n) = log m + log n. That sum reads like a recipe: log m work on the rows, then log n work on the columns. That's exactly Part A.

4Building the logic: which property decides the order?

Property 2 talks about rows: "a row's first value is bigger than the previous row's last value". That's what makes the row-by-row line sorted. So the rows are the "blocks" we can pick between, and we pick the row first.

Now imagine the property were about columns instead: "a column's first value is bigger than the previous column's last value". The teacher builds such a matrix:

          col 0   1   2   3
  row 0  [  1    5   8  11 ]
  row 1  [  3    6   9  12 ]
  row 2  [  4    7  10  13 ]

  read column by column: 1 3 4 | 5 6 7 | 8 9 10 | 11 12 13   sorted ✓
  (5 > 4, 8 > 7, 11 > 10: each column starts after the previous one ends)

Rows are still sorted and columns are still sorted, but now the matrix opens up into a sorted line column by column. So we flip the plan: first binary search the columns to find the valid column, then binary search down that column. The cost is log n + log m, the same total.

Doubt: in the LeetCode 74 matrix, can I search a column first anyway?
→ No. Read column 0 of the 74 matrix: 1, 10, 23. Column 1 starts with 3, which is smaller than 23, the end of column 0. Columns there don't sit one after another, so "does the target fit between this column's first and last value?" can point to the wrong column. For example, target 10: the mid column is column 1 (3, 11, 30), and 3 ≤ 10 ≤ 30, so we'd pick column 1, search it, and say False. But 10 is sitting in column 0. Use the property the question gives you.

5Approach steps (column-first twin)

  1. Binary search over columns 0 … n-1. For the mid column, compare the target with its top value matrix[0][mid] and bottom value matrix[m-1][mid].
  2. Fits → that's the valid column. Smaller than the top → go left. Else → go right.
  3. No valid column → False. Otherwise binary search rows 0 … m-1 inside that column.

6Code (Python)

column-first twin (only for a matrix sorted column after column)
def search_column_major(matrix, target):
    m, n = len(matrix), len(matrix[0])
    left, right = 0, n - 1
    col = -1
    while left <= right:                     # search 1: the valid column
        mid = left + (right - left) // 2
        if matrix[0][mid] <= target <= matrix[m - 1][mid]:
            col = mid
            break
        elif target < matrix[0][mid]:
            right = mid - 1
        else:
            left = mid + 1
    if col == -1:
        return False
    top, bottom = 0, m - 1
    while top <= bottom:                     # search 2: down that column
        mid = top + (bottom - top) // 2
        if matrix[mid][col] == target:
            return True
        elif matrix[mid][col] < target:
            top = mid + 1
        else:
            bottom = mid - 1
    return False
Extra (not in the video): since the opened-up line is one sorted array, can I do a single binary search over indexes 0 … m·n − 1?
→ Yes. Index i in the long line sits at row i // n, column i % n. Same O(log(m·n)) time. It's a nice follow-up to mention in an interview, but the two-step version is what the teacher teaches and it's easier to reason about.
extra: one binary search on the "opened up" index
def search_flat(matrix, target):
    m, n = len(matrix), len(matrix[0])
    low, high = 0, m * n - 1
    while low <= high:
        mid = low + (high - low) // 2
        value = matrix[mid // n][mid % n]    # turn the line index into (row, col)
        if value == target:
            return True
        elif value < target:
            low = mid + 1
        else:
            high = mid - 1
    return False

7Code line by line (what changes from Part A)

linewhat changes
matrix[0][mid] … matrix[m - 1][mid]A column's smallest value is at the top and its biggest at the bottom (in Part A it was the first and last value of a row).
right = mid - 1 / left = mid + 1We throw away columns to the right / left instead of rows below / above.
matrix[mid][col]In search 2 the column is fixed and the row moves.

8Dry run (column-first twin, target 9)

stepleftrightmid coltop … bottomdecision
10315 … 79 > 7 → left = 2
22328 … 10fits → col = 2

Down column 2 = 8, 9, 10: top 0, bottom 2 → mid 1 → 9 == 9 → True.

9Complexity & remember

RememberSearch first along whatever the "starts after the previous one ends" property talks about. Rows in LeetCode 74 → rows first. If it were columns → columns first.

Part C · Revision page

search 1search 2
searches overrow numbers 0 … m−1column numbers 0 … n−1 of the valid row
compares target withfirst and last value of the mid rowthe single value matrix[row][mid]
"found" meansfirst ≤ target ≤ last → save the row, breakequal → return True
go up / left whentarget < first valuevalue > target
go down / right whenelse (target > last value)value < target
nothing foundrow stays −1 → return Falsereturn False
costlog mlog n
approachtimespaceallowed?
linear scan of every cellO(m·n) = 10⁴O(1)passes, but ignores the required log(m·n)
binary search rows, then the rowO(log m + log n)O(1)what the question wants
If you remember only 5 lines 1. The two properties make the matrix one sorted line when read row by row.
2. Search 1 over rows finds the valid row: the only row the target could be in.
3. Check "first ≤ target ≤ last" before the up/down checks, then break.
4. Search 2 is a plain binary search inside that row.
5. log m + log n = log(m·n). If the property were about columns, search columns first.
Mistakes to avoid ✗ while top < bottom (skips the last row left, so target 3 fails); use <=
✗ only comparing with the first value of the row (can't tell "this row" from "below")
✗ moving to mid instead of mid ± 1 (can loop forever)
✗ forgetting the row == -1 check (then matrix[-1] silently reads the last row in Python)
✗ returning −1 instead of False
test it yourself (paste under the Part A solution)
matrix = [[1, 3, 5, 7], [10, 11, 16, 20], [23, 30, 34, 60]]
s = Solution()
print(s.searchMatrix(matrix, 3))    # True
print(s.searchMatrix(matrix, 11))   # True
print(s.searchMatrix(matrix, 23))   # True
print(s.searchMatrix(matrix, 4))    # False  (valid row 0, value missing)
print(s.searchMatrix(matrix, 61))   # False  (no valid row)
print(s.searchMatrix([[1]], 1))     # True   (needs <= in the loops)

Based on this video: Search a 2D Matrix | Binary Search