74. Search a 2D Matrix
Problem Description
You have a 2D matrix of integers with m
rows and n
columns that has two special properties:
- Each row is sorted in non-decreasing order (elements go from smallest to largest or stay the same as you move from left to right)
- The first element of each row is greater than the last element of the previous row
Your task is to determine whether a given target
integer exists anywhere in this matrix. Return true
if the target is found, false
otherwise.
The key constraint is that your solution must run in O(log(m * n))
time complexity, which means you need to use an efficient search algorithm rather than checking every element.
For example, if you have a matrix like:
[ [1, 3, 5, 7], [10, 11, 16, 20], [23, 30, 34, 60] ]
And target = 3
, the function should return true
because 3 exists in the matrix.
If target = 13
, the function should return false
because 13 is not in the matrix.
The solution treats the 2D matrix as a virtual 1D sorted array by using index mapping. Since the matrix has the property that all elements are in sorted order when read row by row, we can apply binary search on this virtual array. The mapping between 1D index and 2D coordinates is done using division and modulo operations: for a 1D index i
, the row is i // n
and the column is i % n
.
Intuition
The key observation is that the matrix has a very special structure - if you read the elements row by row from left to right, top to bottom, you get a completely sorted sequence. This is because each row is sorted, and the first element of each row is greater than the last element of the previous row.
For instance, in the matrix:
[1, 3, 5, 7] [10, 11, 16, 20] [23, 30, 34, 60]
Reading row by row gives us: [1, 3, 5, 7, 10, 11, 16, 20, 23, 30, 34, 60]
- a sorted array!
Since we need O(log(m * n))
time complexity and we're searching in what is essentially a sorted array, binary search naturally comes to mind. But how do we apply binary search on a 2D matrix?
The clever insight is that we don't need to physically convert the 2D matrix into a 1D array. Instead, we can treat it as a virtual 1D array and map between 1D indices and 2D coordinates on the fly.
If we think of the matrix as a 1D array of length m * n
, then:
- Index 0 corresponds to position
(0, 0)
in the matrix - Index 1 corresponds to position
(0, 1)
in the matrix - Index
n
corresponds to position(1, 0)
in the matrix (start of second row)
The mapping formula is straightforward:
- For a 1D index
mid
, the row number ismid // n
(integer division) - The column number is
mid % n
(remainder)
This allows us to perform standard binary search on the range [0, m*n-1]
while accessing elements from the 2D matrix using the coordinate transformation. The beauty of this approach is that it leverages the sorted nature of the data without any preprocessing or extra space.
Learn more about Binary Search patterns.
Solution Approach
The implementation uses binary search on a virtual 1D array representation of the 2D matrix. Here's how the algorithm works step by step:
Initial Setup:
- Get the dimensions:
m
(number of rows) andn
(number of columns) - Set binary search boundaries:
left = 0
andright = m * n - 1
- These boundaries represent the start and end of our virtual 1D array
Binary Search Loop: The algorithm performs a standard binary search with a slight modification:
-
Calculate the middle index:
mid = (left + right) >> 1
(using bit shift for division by 2) -
Convert the 1D index to 2D coordinates using
divmod(mid, n)
:x = mid // n
gives us the row indexy = mid % n
gives us the column index
-
Compare the element at position
(x, y)
with the target:- If
matrix[x][y] >= target
: Move the right boundary tomid
(element could be at mid or to the left) - If
matrix[x][y] < target
: Move the left boundary tomid + 1
(element must be to the right)
- If
Finding the Target:
The loop continues until left >= right
, meaning we've narrowed down to a single position. This binary search variant finds the leftmost position where an element is greater than or equal to the target.
Final Check:
After the loop ends, left
points to the position where the target should be if it exists. We convert this 1D index back to 2D coordinates using:
- Row:
left // n
- Column:
left % n
Then we check if the element at this position equals the target and return the result.
Time Complexity: O(log(m * n))
- We perform binary search on m * n
elements
Space Complexity: O(1)
- Only using a few variables for indices and coordinates
The elegance of this solution lies in treating the 2D matrix as a 1D sorted array without actually reshaping it, achieving optimal time complexity while maintaining constant space usage.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through searching for target = 11
in this matrix:
[ [1, 4, 7], [10, 11, 13], [15, 18, 21] ]
Setup:
- Matrix dimensions:
m = 3
rows,n = 3
columns - Virtual 1D array has
3 * 3 = 9
elements - Binary search range:
left = 0
,right = 8
Iteration 1:
- Calculate middle:
mid = (0 + 8) >> 1 = 4
- Convert to 2D:
divmod(4, 3)
→ row = 1, col = 1 - Check
matrix[1][1] = 11
- Since
11 >= 11
(target), updateright = 4
- Range becomes
[0, 4]
Iteration 2:
- Calculate middle:
mid = (0 + 4) >> 1 = 2
- Convert to 2D:
divmod(2, 3)
→ row = 0, col = 2 - Check
matrix[0][2] = 7
- Since
7 < 11
, updateleft = 2 + 1 = 3
- Range becomes
[3, 4]
Iteration 3:
- Calculate middle:
mid = (3 + 4) >> 1 = 3
- Convert to 2D:
divmod(3, 3)
→ row = 1, col = 0 - Check
matrix[1][0] = 10
- Since
10 < 11
, updateleft = 3 + 1 = 4
- Range becomes
[4, 4]
Loop ends (left >= right)
Final Check:
left = 4
- Convert to 2D: row =
4 // 3 = 1
, col =4 % 3 = 1
- Check
matrix[1][1] = 11
- Since
11 == 11
, returntrue
The virtual 1D array mapping works as follows:
Index: 0 1 2 3 4 5 6 7 8 Value: 1 4 7 10 11 13 15 18 21 └─row 0─┘└──row 1──┘└──row 2──┘
Solution Implementation
1class Solution:
2 def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
3 # Get dimensions of the matrix
4 num_rows, num_cols = len(matrix), len(matrix[0])
5
6 # Initialize binary search boundaries
7 # Treat the 2D matrix as a flattened 1D array
8 left, right = 0, num_rows * num_cols - 1
9
10 # Binary search on the flattened array
11 while left < right:
12 # Calculate middle index
13 mid = (left + right) >> 1 # Bit shift for division by 2
14
15 # Convert 1D index to 2D coordinates
16 # row = mid // num_cols, col = mid % num_cols
17 row, col = divmod(mid, num_cols)
18
19 # Compare middle element with target
20 if matrix[row][col] >= target:
21 # Target is in the left half (including mid)
22 right = mid
23 else:
24 # Target is in the right half (excluding mid)
25 left = mid + 1
26
27 # Check if the element at the final position equals target
28 # Convert 1D index back to 2D coordinates
29 return matrix[left // num_cols][left % num_cols] == target
30
1class Solution {
2 public boolean searchMatrix(int[][] matrix, int target) {
3 // Get dimensions of the matrix
4 int rows = matrix.length;
5 int cols = matrix[0].length;
6
7 // Initialize binary search boundaries
8 // Treat the 2D matrix as a flattened 1D array
9 int left = 0;
10 int right = rows * cols - 1;
11
12 // Binary search to find the target or its insertion position
13 while (left < right) {
14 // Calculate middle index (using bit shift for division by 2)
15 int mid = (left + right) >> 1;
16
17 // Convert 1D index to 2D coordinates
18 int row = mid / cols;
19 int col = mid % cols;
20
21 // Compare middle element with target
22 if (matrix[row][col] >= target) {
23 // Target could be at mid or to the left
24 right = mid;
25 } else {
26 // Target must be to the right of mid
27 left = mid + 1;
28 }
29 }
30
31 // Check if the element at the final position equals target
32 // Convert the final 1D index back to 2D coordinates
33 return matrix[left / cols][left % cols] == target;
34 }
35}
36
1class Solution {
2public:
3 bool searchMatrix(vector<vector<int>>& matrix, int target) {
4 // Get dimensions of the matrix
5 int rows = matrix.size();
6 int cols = matrix[0].size();
7
8 // Initialize binary search boundaries
9 // Treat the 2D matrix as a 1D sorted array
10 int left = 0;
11 int right = rows * cols - 1;
12
13 // Binary search to find the target element
14 while (left < right) {
15 // Calculate middle index (using bit shift for division by 2)
16 int mid = left + (right - left) / 2;
17
18 // Convert 1D index to 2D coordinates
19 int row = mid / cols;
20 int col = mid % cols;
21
22 // Compare middle element with target
23 if (matrix[row][col] >= target) {
24 // Target is in the left half (including mid)
25 right = mid;
26 } else {
27 // Target is in the right half (excluding mid)
28 left = mid + 1;
29 }
30 }
31
32 // Check if the element at the final position equals target
33 // Convert final 1D index back to 2D coordinates
34 return matrix[left / cols][left % cols] == target;
35 }
36};
37
1/**
2 * Searches for a target value in a 2D matrix using binary search.
3 * The matrix has the following properties:
4 * - Each row is sorted in ascending order
5 * - The first element of each row is greater than the last element of the previous row
6 *
7 * @param matrix - The 2D matrix to search in
8 * @param target - The target value to find
9 * @returns true if target exists in matrix, false otherwise
10 */
11function searchMatrix(matrix: number[][], target: number): boolean {
12 // Get matrix dimensions
13 const rowCount: number = matrix.length;
14 const columnCount: number = matrix[0].length;
15
16 // Initialize binary search boundaries
17 // Treat the 2D matrix as a flattened 1D array
18 let leftBoundary: number = 0;
19 let rightBoundary: number = rowCount * columnCount;
20
21 // Perform binary search
22 while (leftBoundary < rightBoundary) {
23 // Calculate middle index using unsigned right shift to avoid overflow
24 const middleIndex: number = (leftBoundary + rightBoundary) >>> 1;
25
26 // Convert 1D index to 2D coordinates
27 const rowIndex: number = Math.floor(middleIndex / columnCount);
28 const columnIndex: number = middleIndex % columnCount;
29
30 // Check if we found the target
31 if (matrix[rowIndex][columnIndex] === target) {
32 return true;
33 }
34
35 // Adjust search boundaries based on comparison
36 if (matrix[rowIndex][columnIndex] < target) {
37 // Target is in the right half
38 leftBoundary = middleIndex + 1;
39 } else {
40 // Target is in the left half
41 rightBoundary = middleIndex;
42 }
43 }
44
45 // Target not found in matrix
46 return false;
47}
48
Time and Space Complexity
The time complexity is O(log(m × n))
, where m
is the number of rows and n
is the number of columns in the matrix. This is because the algorithm performs binary search on a virtual flattened array of size m × n
. Binary search divides the search space in half with each iteration, resulting in logarithmic time complexity relative to the total number of elements.
The space complexity is O(1)
as the algorithm only uses a constant amount of extra space for variables (left
, right
, mid
, x
, y
) regardless of the input size. No additional data structures are created that scale with the input.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Index Out of Bounds Error
The Problem: The most common pitfall occurs when the matrix is empty or has empty rows. The code len(matrix[0])
will throw an IndexError if the matrix is empty, and the final return statement matrix[left // num_cols][left % num_cols]
can cause an index out of bounds error if not properly handled.
Example Case:
- Empty matrix:
matrix = []
- Matrix with empty rows:
matrix = [[]]
Solution: Add validation checks at the beginning of the function:
def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
# Handle edge cases
if not matrix or not matrix[0]:
return False
num_rows, num_cols = len(matrix), len(matrix[0])
# ... rest of the code
2. Incorrect Binary Search Boundary Update
The Problem: Using right = mid - 1
instead of right = mid
when matrix[row][col] >= target
. This can cause the algorithm to miss the target element because the standard binary search pattern doesn't apply directly here - we're looking for the leftmost position where an element is >= target, not just any position.
Incorrect Implementation:
if matrix[row][col] >= target: right = mid - 1 # WRONG! This might skip over the target
Correct Implementation:
if matrix[row][col] >= target: right = mid # Keep mid as a potential answer
3. Integer Overflow in Different Languages
The Problem: While Python handles large integers automatically, if you're implementing this in languages like Java or C++, calculating mid = (left + right) / 2
can cause integer overflow when left
and right
are large.
Solution for Other Languages:
Use mid = left + (right - left) / 2
or bit manipulation mid = (left + right) >> 1
with unsigned integers to avoid overflow.
4. Confusion Between Row-Major and Column-Major Ordering
The Problem: Incorrectly mapping between 1D and 2D indices by swapping the division and modulo operations. This happens when developers confuse row-major ordering (used here) with column-major ordering.
Wrong Mapping:
row, col = divmod(mid, num_rows) # WRONG! Should divide by num_cols
Correct Mapping:
row, col = divmod(mid, num_cols) # Correct: row = mid // num_cols, col = mid % num_cols
5. Not Handling Duplicate Values Correctly
The Problem: While the current implementation works for finding any occurrence of the target, if you need to find a specific occurrence (first or last) of duplicate values, the boundary update logic needs adjustment.
Enhanced Solution for Finding First Occurrence:
def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
if not matrix or not matrix[0]:
return False
num_rows, num_cols = len(matrix), len(matrix[0])
left, right = 0, num_rows * num_cols - 1
result = -1
while left <= right:
mid = (left + right) >> 1
row, col = divmod(mid, num_cols)
if matrix[row][col] == target:
result = mid
right = mid - 1 # Continue searching left for first occurrence
elif matrix[row][col] < target:
left = mid + 1
else:
right = mid - 1
return result != -1
Which of the two traversal algorithms (BFS and DFS) can be used to find whether two nodes are connected?
Recommended Readings
Binary Search Speedrun For each of the Speedrun questions you will be given a binary search related problem and a corresponding multiple choice question The multiple choice questions are related to the techniques and template s introduced in the binary search section It's recommended that you have gone through at
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!