Facebook Pixel

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:

  1. 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)
  2. 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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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 is mid // 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
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        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}
38
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        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};
39
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    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}
46

Solution 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) and n (number of columns)
  • Set binary search boundaries: left = 0 and right = m * n - 1
  • Initialize first_true_index = -1 to track the first position where element >= 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:

  1. Calculate the middle index: mid = (left + right) // 2

  2. Convert the 1D index to 2D coordinates:

    • row = mid // n gives us the row index
    • col = mid % n gives us the column index
  3. Check if feasible (element >= target):

    • If matrix[row][col] >= target: Record first_true_index = mid, then search left with right = mid - 1
    • If matrix[row][col] < target: Search right with left = mid + 1

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, return false
  • 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 Evaluator

Example 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
  • 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
Loading...
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

What are the most two important steps in writing a depth first search function? (Select 2)


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More