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
ncorresponds 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)
We can define a feasible function: feasible(i) = matrix[i // n][i % n] >= target. This creates a pattern of [false, false, ..., true, true, ...] across the virtual 1D array. The first true position is where the target would be if it exists.
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 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 first_true_index = -1
10
11 # Binary search using the template: find first index where element >= target
12 while left <= right:
13 mid = (left + right) // 2
14
15 # Convert 1D index to 2D coordinates
16 row, col = divmod(mid, num_cols)
17
18 # Feasible condition: matrix[row][col] >= target
19 if matrix[row][col] >= target:
20 first_true_index = mid
21 right = mid - 1
22 else:
23 left = mid + 1
24
25 # Check if first_true_index points to the target
26 if first_true_index == -1:
27 return False
28 row, col = divmod(first_true_index, num_cols)
29 return matrix[row][col] == target
301class 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 int left = 0;
9 int right = rows * cols - 1;
10 int firstTrueIndex = -1;
11
12 // Binary search using the template: find first index where element >= target
13 while (left <= right) {
14 int mid = left + (right - left) / 2;
15
16 // Convert 1D index to 2D coordinates
17 int row = mid / cols;
18 int col = mid % cols;
19
20 // Feasible condition: matrix[row][col] >= target
21 if (matrix[row][col] >= target) {
22 firstTrueIndex = mid;
23 right = mid - 1;
24 } else {
25 left = mid + 1;
26 }
27 }
28
29 // Check if firstTrueIndex points to the target
30 if (firstTrueIndex == -1) {
31 return false;
32 }
33 int row = firstTrueIndex / cols;
34 int col = firstTrueIndex % cols;
35 return matrix[row][col] == target;
36 }
37}
381class 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 int left = 0;
10 int right = rows * cols - 1;
11 int firstTrueIndex = -1;
12
13 // Binary search using the template: find first index where element >= target
14 while (left <= right) {
15 int mid = left + (right - left) / 2;
16
17 // Convert 1D index to 2D coordinates
18 int row = mid / cols;
19 int col = mid % cols;
20
21 // Feasible condition: matrix[row][col] >= target
22 if (matrix[row][col] >= target) {
23 firstTrueIndex = mid;
24 right = mid - 1;
25 } else {
26 left = mid + 1;
27 }
28 }
29
30 // Check if firstTrueIndex points to the target
31 if (firstTrueIndex == -1) {
32 return false;
33 }
34 int row = firstTrueIndex / cols;
35 int col = firstTrueIndex % cols;
36 return matrix[row][col] == target;
37 }
38};
391/**
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 let left: number = 0;
18 let right: number = rowCount * columnCount - 1;
19 let firstTrueIndex: number = -1;
20
21 // Binary search using the template: find first index where element >= target
22 while (left <= right) {
23 const mid: number = Math.floor((left + right) / 2);
24
25 // Convert 1D index to 2D coordinates
26 const row: number = Math.floor(mid / columnCount);
27 const col: number = mid % columnCount;
28
29 // Feasible condition: matrix[row][col] >= target
30 if (matrix[row][col] >= target) {
31 firstTrueIndex = mid;
32 right = mid - 1;
33 } else {
34 left = mid + 1;
35 }
36 }
37
38 // Check if firstTrueIndex points to the target
39 if (firstTrueIndex === -1) {
40 return false;
41 }
42 const row: number = Math.floor(firstTrueIndex / columnCount);
43 const col: number = firstTrueIndex % columnCount;
44 return matrix[row][col] === target;
45}
46Solution Approach
The implementation uses the standard binary search template 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 = 0andright = m * n - 1 - Initialize
first_true_index = -1to track the first position whereelement >= target
Feasible Function:
For any index mid, the feasible condition is: matrix[mid // n][mid % n] >= target
This creates a pattern like [false, false, false, true, true, true] where we want to find the first true.
Binary Search Loop:
Using while left <= right:
-
Calculate the middle index:
mid = (left + right) // 2 -
Convert the 1D index to 2D coordinates:
row = mid // ngives us the row indexcol = mid % ngives us the column index
-
Check if feasible (element >= target):
- If
matrix[row][col] >= target: Recordfirst_true_index = mid, then search left withright = mid - 1 - If
matrix[row][col] < target: Search right withleft = mid + 1
- If
Final Check:
After the loop, first_true_index holds the position of the first element >= target. We check if that element equals the target:
- If
first_true_index == -1, the target is larger than all elements, returnfalse - Otherwise, convert to 2D coordinates and check if
matrix[first_true_index // n][first_true_index % n] == target
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 = 3rows,n = 3columns - Virtual 1D array has
3 * 3 = 9elements - Binary search range:
left = 0,right = 8 first_true_index = -1- Feasible condition:
matrix[mid // 3][mid % 3] >= 11
Iteration 1:
left = 0,right = 8- Calculate middle:
mid = (0 + 8) // 2 = 4 - Convert to 2D: row = 1, col = 1
- Check
matrix[1][1] = 11 >= 11? Yes (feasible) - Update
first_true_index = 4,right = mid - 1 = 3
Iteration 2:
left = 0,right = 3- Calculate middle:
mid = (0 + 3) // 2 = 1 - Convert to 2D: row = 0, col = 1
- Check
matrix[0][1] = 4 >= 11? No - Update
left = mid + 1 = 2
Iteration 3:
left = 2,right = 3- Calculate middle:
mid = (2 + 3) // 2 = 2 - Convert to 2D: row = 0, col = 2
- Check
matrix[0][2] = 7 >= 11? No - Update
left = mid + 1 = 3
Iteration 4:
left = 3,right = 3- Calculate middle:
mid = (3 + 3) // 2 = 3 - Convert to 2D: row = 1, col = 0
- Check
matrix[1][0] = 10 >= 11? No - Update
left = mid + 1 = 4
Loop ends (left > right)
Final Check:
first_true_index = 4- Convert to 2D: row =
4 // 3 = 1, col =4 % 3 = 1 - Check
matrix[1][1] = 11 == 11? Yes - Return
true
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──┘
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. Using Wrong Binary Search Template Variant
The Problem: Using while left < right with right = mid instead of the standard template. This alternative variant works but is harder to reason about and inconsistent with the canonical pattern.
Incorrect Implementation:
while left < right: mid = (left + right) // 2 if matrix[row][col] >= target: right = mid # Wrong variant! else: left = mid + 1 return matrix[left // cols][left % cols] == target
Correct Template Implementation:
first_true_index = -1 while left <= right: mid = (left + right) // 2 if matrix[row][col] >= target: first_true_index = mid right = mid - 1 else: left = mid + 1 return first_true_index != -1 and matrix[...] == target
2. 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.
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
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 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. Forgetting to Check if first_true_index is -1
The Problem: When using the template, if first_true_index is never updated (target is larger than all elements), accessing matrix[first_true_index // cols][...] will cause an error.
Solution: Always check for the -1 case:
if first_true_index == -1:
return False
row, col = divmod(first_true_index, num_cols)
return matrix[row][col] == target
What are the most two important steps in writing a depth first search function? (Select 2)
Recommended Readings
https assets algo monster cover_photos Binary_Search svg Binary Search Intuition Binary search is an efficient array search algorithm It works by narrowing down the search range by half each time If you have looked up a word in a physical dictionary you've already used binary search in real life Let's
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 If you prefer videos here's a video that explains recursion in a fun and easy way 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 him
Want a Structured Path to Master System Design Too? Don’t Miss This!