Search a 2D Matrix

Medium Binary Search

Problem

Given an m x n matrix where each row is sorted in ascending order and the first integer of each row is greater than the last integer of the previous row, return true if target is in the matrix or false otherwise.

  • m == matrix.length
  • n == matrix[i].length
  • 1 ≤ m, n ≤ 100
  • −10⁴ ≤ matrix[i][j], target ≤ 10⁴
  • Each row is sorted in ascending order.
  • The first integer of each row is greater than the last integer of the previous row.

Example

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

The algorithm first performs a binary search over the rows to find the candidate row that could contain the target. For target = 3, it compares 3 with the last element of the middle row (row 1: 20). Since 3 < 20, it narrows the search to the top half. It then checks if 3 is greater than or equal to the first element of the candidate row. Once the candidate row is found (row 0), a second binary search is performed within that row. The target 3 is found at index 1, so the algorithm returns true.

Approach

Straightforward Solution

A brute-force approach would scan every element in the matrix, resulting in O(m*n) time complexity, which is inefficient for large matrices.

Core Observation

The matrix can be viewed as a sorted list of rows, each sorted internally, with the property that the first element of a row is greater than the last element of the previous row. This allows a two-level binary search: first to find the candidate row, then to find the target within that row.

Path to Optimal

The key recognition signals are 'sorted rows' and 'first integer of each row greater than last integer of previous row'. These indicate a Modified Binary Search because the matrix can be treated as a sorted sequence of rows, enabling binary search to find the candidate row, followed by a standard binary search within that row. This reduces the search space from O(m*n) to O(log m + log n).

Optimal Approach

Perform a binary search on the rows to find the row where the target could reside by comparing the target with the first and last elements of the middle row. Once the candidate row is identified, perform a binary search within that row to find the target. Return true if found, otherwise false.

Time

O(log m + log n)

The algorithm performs two binary searches: one over m rows and one over n columns. Each binary search takes O(log m) and O(log n) respectively, resulting in O(log m + log n) total time.

Space

O(1)

The algorithm uses only a fixed number of variables for indices and does not allocate additional data structures proportional to input size, resulting in constant auxiliary space.

Pattern Spotlight

Modified Binary Search (Two-Level Search in Sorted Matrix)

When a 2D matrix has sorted rows and each row's first element is greater than the previous row's last, treat the matrix as a sorted list of rows and apply binary search twice: first to locate the candidate row, then to locate the target within that row.

Solution

1class Solution:
2 def searchMatrix(self, matrix: list[list[int]], target: int) -> bool:
3 ROWS, COLS = len(matrix), len(matrix[0])
4 top, bot = 0, ROWS - 1
5
6 while top <= bot:
7 row = (top + bot) // 2
8 if target > matrix[row][-1]:
9 top = row + 1
10 elif target < matrix[row][0]:
11 bot = row - 1
12 else:
13 break
14
15 if not (top <= bot):
16 return False
17
18 row = (top + bot) // 2
19 l, r = 0, COLS - 1
20 while l <= r:
21 m = (l + r) // 2
22 if target > matrix[row][m]:
23 l = m + 1
24 elif target < matrix[row][m]:
25 r = m - 1
26 else:
27 return True
28
29 return False

Step-by-Step Solution

1

Locate Candidate Row Using Binary Search on Row Boundaries

3ROWS, COLS = len(matrix), len(matrix[0])
4top, bot = 0, ROWS - 1
6while top <= bot:
7 row = (top + bot) // 2
8 if target > matrix[row][-1]:
9 top = row + 1
10 elif target < matrix[row][0]:
11 bot = row - 1
12 else:
13 break

Objective

To identify the row that could contain the target by comparing the target with the first and last elements of the middle row.

Key Insight

Because each row is sorted and the first element of each row is greater than the last element of the previous row, the target must lie within a single row whose first element is less than or equal to the target and last element is greater than or equal to the target. Binary searching over the rows using these boundary checks efficiently narrows down the candidate row in O(log m) time.

Interview Quick-Check

Core Logic

Binary search over rows compares the target with the first and last elements of the middle row to decide whether to move up or down the row range.

State & Boundaries

The loop continues while top <= bot, ensuring all candidate rows are considered.

Common Pitfalls & Bugs

Failing to check both the first and last elements of the row can lead to incorrect candidate row selection.

2

Confirm Candidate Row and Return False if None Found

15if not (top <= bot):
16 return False

Objective

To verify if a candidate row was found and return false immediately if the target cannot be in any row.

Key Insight

If the binary search over rows completes without finding a row whose boundaries contain the target, the target is not present in the matrix. This early exit prevents unnecessary further searching and ensures correctness.

Interview Quick-Check

State & Boundaries

Check if top <= bot after the row search loop to confirm candidate row existence.

Common Pitfalls & Bugs

Neglecting this check can lead to searching an invalid row index, causing errors or incorrect results.

3

Perform Binary Search Within Candidate Row to Find Target

18row = (top + bot) // 2
19l, r = 0, COLS - 1
20while l <= r:
21 m = (l + r) // 2
22 if target > matrix[row][m]:
23 l = m + 1
24 elif target < matrix[row][m]:
25 r = m - 1
26 else:
27 return True

Objective

To locate the target within the identified candidate row using standard binary search.

Key Insight

Once the candidate row is identified, the problem reduces to a classic binary search within a sorted array. This step efficiently finds the target in O(log n) time by comparing the target with the middle element and adjusting the search boundaries accordingly.

Interview Quick-Check

Core Logic

Binary search within the row compares the target with the middle element and moves left or right pointers accordingly.

State & Boundaries

The loop continues while l <= r, ensuring all elements in the row are considered.

Common Pitfalls & Bugs

Returning true immediately upon finding the target avoids unnecessary further searching.

4

Return False When Target is Not Found in Candidate Row

29return False

Objective

To return false after exhausting the binary search within the candidate row without finding the target.

Key Insight

If the binary search completes without locating the target, it confirms the target is not present in the matrix. Returning false here correctly signals the absence of the target.

Interview Quick-Check

State & Boundaries

Return false only after the binary search loop ends without a match.

Line Analysis

This solution has 6 Critical lines interviewers watch for.

Line 27 Critical
return True

Return true when the target is found.

Finding the target confirms its presence in the matrix and ends the search immediately.

Line 13 Critical
break

Stop the row search once a candidate row is found.

Breaking preserves the identified candidate row for the second binary search.

Line 15 Critical
if not (top <= bot):

Check whether no candidate row was found.

If the row search ended without a candidate row, the target cannot be in the matrix.

Line 16 Critical
return False

Return false when no candidate row exists.

Returning immediately prevents searching inside an invalid row.

Line 26 Critical
else:

Enter the successful match branch.

Reaching this branch means the midpoint value matches the target exactly.

Line 29 Critical
return False

Return false when the target is not found.

If the row search and the in-row search both fail, the target is absent from the matrix.

Line 8 Core
if target > matrix[row][-1]:

Check whether the target is larger than the current row's last element.

If the target exceeds the last value in the current row, it must lie in a lower row.

Line 10 Core
elif target < matrix[row][0]:

Check whether the target is smaller than the current row's first element.

If the target is smaller than the first value in the current row, it must lie in a higher row.

Line 12 Core
else:

Enter the candidate-row branch.

Reaching this branch means the target lies within the current row's value range.

Line 22 Core
if target > matrix[row][m]:

Check whether the target is larger than the midpoint value.

If the target is larger than the midpoint value, it must lie to the right.

Line 24 Core
elif target < matrix[row][m]:

Check whether the target is smaller than the midpoint value.

If the target is smaller than the midpoint value, it must lie to the left.

Line 7 Core
row = (top + bot) // 2

Compute the middle row index.

Selecting the middle row halves the remaining row search space.

Line 9 Core
top = row + 1

Move the top pointer below the current row.

Moving the top pointer down discards rows that are definitely too small.

Line 11 Core
bot = row - 1

Move the bottom pointer above the current row.

Moving the bottom pointer up discards rows that are definitely too large.

Line 21 Core
m = (l + r) // 2

Compute the middle column index.

Selecting the middle column halves the remaining search space in the row.

Line 23 Core
l = m + 1

Move the left pointer rightward.

Moving left rightward discards values that are definitely too small.

Line 25 Core
r = m - 1

Move the right pointer leftward.

Moving right leftward discards values that are definitely too large.

Line 6 Standard
while top <= bot:

Binary search across the rows.

The loop continues while there are still candidate rows to inspect.

Line 18 Standard
row = (top + bot) // 2

Select the candidate row.

This recomputes the row that survived the first binary search.

Line 20 Standard
while l <= r:

Binary search within the candidate row.

The second binary search checks the sorted candidate row for the target.

Line 3 Standard
ROWS, COLS = len(matrix), len(matrix[0])

Calculate the number of rows and columns in the matrix.

Knowing the matrix dimensions is essential for setting the boundaries of the two binary searches.

Line 4 Standard
top, bot = 0, ROWS - 1

Initialize the top and bottom row pointers.

These pointers define the initial search space over all rows.

Line 19 Standard
l, r = 0, COLS - 1

Initialize the left and right column pointers.

These pointers define the search interval inside the chosen row.

Test Your Understanding

Why is it necessary to perform two separate binary searches instead of a single binary search treating the matrix as a flattened sorted array?

Because the matrix is sorted row-wise with the property that each row's first element is greater than the previous row's last, it can be treated as a sorted list of rows. Performing two binary searches leverages this structure explicitly and avoids the complexity of index mapping required for a single binary search on a flattened array, simplifying implementation and maintaining O(log m + log n) time.

Related Problems

Binary Search pattern

Don't just read it. Drill it.

Reconstruct Search a 2D Matrix from memory until it sticks. AlgoDrill blanks out key lines and makes you fill them back in, step by step.

Unlock the Search a 2D Matrix drill

or drill a free problem