Facebook Pixel

302. Smallest Rectangle Enclosing Black Pixels

Problem Description

You have a binary matrix image of size m x n where each cell contains either '0' (white pixel) or '1' (black pixel).

The key properties of this matrix are:

  • All black pixels ('1') form a single connected region
  • Black pixels are connected horizontally and vertically (not diagonally)
  • You're given coordinates (x, y) of one black pixel in this region

Your task is to find the area of the smallest axis-aligned rectangle that can enclose all the black pixels in the matrix.

For example, if you have black pixels scattered in various positions, you need to find the tightest bounding box that contains all of them. The area would be calculated as (height × width) of this bounding rectangle.

Important constraint: Your algorithm must run in less than O(mn) time complexity, meaning you cannot simply scan through every cell in the matrix.

The solution uses binary search to efficiently find the boundaries of the black pixel region:

  • Finds the topmost row containing a black pixel (upper boundary u)
  • Finds the bottommost row containing a black pixel (lower boundary d)
  • Finds the leftmost column containing a black pixel (left boundary l)
  • Finds the rightmost column containing a black pixel (right boundary r)

The final area is calculated as (d - u + 1) * (r - l + 1), representing the height times the width of the bounding rectangle.

Flowchart Walkthrough

First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:

Is it a graph?

  • Yes: While the problem presents as a 2D matrix, we can conceptualize it as a graph where each pixel is a node and adjacent pixels (horizontally and vertically) form edges. The black pixels form a connected component in this graph.

Is it a tree?

  • No: The pixel connectivity structure is not a tree - it can have cycles. For example, four black pixels forming a square would create a cycle.

Is the problem related to directed acyclic graphs?

  • No: The pixel connectivity is undirected (if pixel A connects to pixel B, then B connects to A).

Is the problem related to shortest paths?

  • No: We're not finding shortest paths between pixels. We're finding the boundaries of a connected region.

Does the problem involve connectivity?

  • Yes: The core property is that all black pixels form a single connected region. We need to explore this connected component to find its boundaries.

Does the problem have small constraints?

  • No: The problem explicitly requires less than O(mn) complexity, suggesting the constraints are not small enough for exhaustive exploration.

Conclusion: Following the flowchart, we arrive at BFS as a potential solution. However, the problem's constraint of less than O(mn) complexity means we cannot use traditional DFS/BFS to explore all black pixels.

Note: While the flowchart suggests BFS for connectivity problems with larger constraints, the actual solution uses binary search instead of DFS/BFS traversal. This is because:

  1. We know there's only one connected black region
  2. We have a starting black pixel coordinate (x, y)
  3. Binary search can find boundaries in O(m log n + n log m) time, which satisfies the complexity requirement

The DFS pattern would typically be used if we needed to explore the entire connected component, but here the optimization comes from using binary search to find the extreme boundaries without visiting every black pixel.

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

Intuition

The key insight comes from understanding what we're really looking for and the properties of the black pixel region.

Since all black pixels form a single connected region, and we need the smallest rectangle that encloses them all, we're essentially finding the extreme boundaries of this region - the topmost, bottommost, leftmost, and rightmost black pixels.

The naive approach would be to perform DFS/BFS from the given black pixel (x, y) to explore all connected black pixels and track the min/max coordinates. However, this would take O(mn) time in the worst case, violating our complexity requirement.

Here's the crucial observation: If we know there's at least one black pixel in a row or column, we can use binary search to find the boundary rows and columns.

Think about finding the topmost row with a black pixel:

  • We know row x has a black pixel (given)
  • We know rows above some boundary have no black pixels
  • We know rows at or below the boundary have at least one black pixel
  • This creates a binary property we can search on!

For each boundary:

  1. Upper boundary: Binary search rows [0, x] to find the smallest row index that contains a black pixel
  2. Lower boundary: Binary search rows [x, m-1] to find the largest row index that contains a black pixel
  3. Left boundary: Binary search columns [0, y] to find the smallest column index that contains a black pixel
  4. Right boundary: Binary search columns [y, n-1] to find the largest column index that contains a black pixel

To check if a row has a black pixel, we scan through all columns in that row (O(n) time). Similarly, checking a column takes O(m) time.

This gives us a total complexity of O(m log n + n log m):

  • Finding horizontal boundaries: 2 binary searches × O(log m) iterations × O(n) per check
  • Finding vertical boundaries: 2 binary searches × O(log n) iterations × O(m) per check

The area of the bounding rectangle is then simply (bottom - top + 1) × (right - left + 1).

Solution Implementation

1class Solution:
2    def minArea(self, image: List[List[str]], x: int, y: int) -> int:
3        rows, cols = len(image), len(image[0])
4
5        def has_black_in_row(row: int) -> bool:
6            """Check if a row contains any black pixel."""
7            for c in range(cols):
8                if image[row][c] == '1':
9                    return True
10            return False
11
12        def has_black_in_col(col: int) -> bool:
13            """Check if a column contains any black pixel."""
14            for r in range(rows):
15                if image[r][col] == '1':
16                    return True
17            return False
18
19        # Find topmost row with black pixel (first row where has_black is True)
20        left, right = 0, x
21        first_true_index = -1
22        while left <= right:
23            mid = (left + right) // 2
24            if has_black_in_row(mid):
25                first_true_index = mid
26                right = mid - 1
27            else:
28                left = mid + 1
29        top_boundary = first_true_index
30
31        # Find bottommost row with black pixel
32        # Search for first row WITHOUT black pixel, then subtract 1
33        left, right = x + 1, rows
34        first_true_index = -1
35        while left <= right:
36            mid = (left + right) // 2
37            if mid >= rows or not has_black_in_row(mid):
38                first_true_index = mid
39                right = mid - 1
40            else:
41                left = mid + 1
42        bottom_boundary = first_true_index - 1 if first_true_index != -1 else rows - 1
43
44        # Find leftmost column with black pixel (first col where has_black is True)
45        left, right = 0, y
46        first_true_index = -1
47        while left <= right:
48            mid = (left + right) // 2
49            if has_black_in_col(mid):
50                first_true_index = mid
51                right = mid - 1
52            else:
53                left = mid + 1
54        left_boundary = first_true_index
55
56        # Find rightmost column with black pixel
57        # Search for first column WITHOUT black pixel, then subtract 1
58        left, right = y + 1, cols
59        first_true_index = -1
60        while left <= right:
61            mid = (left + right) // 2
62            if mid >= cols or not has_black_in_col(mid):
63                first_true_index = mid
64                right = mid - 1
65            else:
66                left = mid + 1
67        right_boundary = first_true_index - 1 if first_true_index != -1 else cols - 1
68
69        # Calculate the area of the bounding rectangle
70        height = bottom_boundary - top_boundary + 1
71        width = right_boundary - left_boundary + 1
72        return height * width
73
1class Solution {
2
3    public int minArea(char[][] image, int x, int y) {
4        int rows = image.length;
5        int cols = image[0].length;
6
7        // Find topmost row with black pixel (first true in feasible pattern)
8        int left = 0, right = x;
9        int firstTrueIndex = -1;
10        while (left <= right) {
11            int mid = left + (right - left) / 2;
12            if (hasBlackInRow(image, mid, cols)) {
13                firstTrueIndex = mid;
14                right = mid - 1;
15            } else {
16                left = mid + 1;
17            }
18        }
19        int topRow = firstTrueIndex;
20
21        // Find bottommost row with black pixel
22        // Search for first row WITHOUT black pixel, then subtract 1
23        left = x + 1;
24        right = rows;
25        firstTrueIndex = -1;
26        while (left <= right) {
27            int mid = left + (right - left) / 2;
28            if (mid >= rows || !hasBlackInRow(image, mid, cols)) {
29                firstTrueIndex = mid;
30                right = mid - 1;
31            } else {
32                left = mid + 1;
33            }
34        }
35        int bottomRow = firstTrueIndex != -1 ? firstTrueIndex - 1 : rows - 1;
36
37        // Find leftmost column with black pixel (first true in feasible pattern)
38        left = 0;
39        right = y;
40        firstTrueIndex = -1;
41        while (left <= right) {
42            int mid = left + (right - left) / 2;
43            if (hasBlackInCol(image, mid, rows)) {
44                firstTrueIndex = mid;
45                right = mid - 1;
46            } else {
47                left = mid + 1;
48            }
49        }
50        int leftCol = firstTrueIndex;
51
52        // Find rightmost column with black pixel
53        // Search for first column WITHOUT black pixel, then subtract 1
54        left = y + 1;
55        right = cols;
56        firstTrueIndex = -1;
57        while (left <= right) {
58            int mid = left + (right - left) / 2;
59            if (mid >= cols || !hasBlackInCol(image, mid, rows)) {
60                firstTrueIndex = mid;
61                right = mid - 1;
62            } else {
63                left = mid + 1;
64            }
65        }
66        int rightCol = firstTrueIndex != -1 ? firstTrueIndex - 1 : cols - 1;
67
68        // Calculate area of the bounding rectangle
69        return (bottomRow - topRow + 1) * (rightCol - leftCol + 1);
70    }
71
72    private boolean hasBlackInRow(char[][] image, int row, int cols) {
73        for (int c = 0; c < cols; c++) {
74            if (image[row][c] == '1') {
75                return true;
76            }
77        }
78        return false;
79    }
80
81    private boolean hasBlackInCol(char[][] image, int col, int rows) {
82        for (int r = 0; r < rows; r++) {
83            if (image[r][col] == '1') {
84                return true;
85            }
86        }
87        return false;
88    }
89}
90
1class Solution {
2public:
3    int minArea(vector<vector<char>>& image, int x, int y) {
4        int rows = image.size();
5        int cols = image[0].size();
6
7        auto hasBlackInRow = [&](int row) -> bool {
8            for (int c = 0; c < cols; ++c) {
9                if (image[row][c] == '1') return true;
10            }
11            return false;
12        };
13
14        auto hasBlackInCol = [&](int col) -> bool {
15            for (int r = 0; r < rows; ++r) {
16                if (image[r][col] == '1') return true;
17            }
18            return false;
19        };
20
21        // Find topmost row with black pixel (first true in feasible pattern)
22        int left = 0, right = x;
23        int firstTrueIndex = -1;
24        while (left <= right) {
25            int mid = left + (right - left) / 2;
26            if (hasBlackInRow(mid)) {
27                firstTrueIndex = mid;
28                right = mid - 1;
29            } else {
30                left = mid + 1;
31            }
32        }
33        int topRow = firstTrueIndex;
34
35        // Find bottommost row with black pixel
36        // Search for first row WITHOUT black pixel, then subtract 1
37        left = x + 1;
38        right = rows;
39        firstTrueIndex = -1;
40        while (left <= right) {
41            int mid = left + (right - left) / 2;
42            if (mid >= rows || !hasBlackInRow(mid)) {
43                firstTrueIndex = mid;
44                right = mid - 1;
45            } else {
46                left = mid + 1;
47            }
48        }
49        int bottomRow = firstTrueIndex != -1 ? firstTrueIndex - 1 : rows - 1;
50
51        // Find leftmost column with black pixel (first true in feasible pattern)
52        left = 0;
53        right = y;
54        firstTrueIndex = -1;
55        while (left <= right) {
56            int mid = left + (right - left) / 2;
57            if (hasBlackInCol(mid)) {
58                firstTrueIndex = mid;
59                right = mid - 1;
60            } else {
61                left = mid + 1;
62            }
63        }
64        int leftCol = firstTrueIndex;
65
66        // Find rightmost column with black pixel
67        // Search for first column WITHOUT black pixel, then subtract 1
68        left = y + 1;
69        right = cols;
70        firstTrueIndex = -1;
71        while (left <= right) {
72            int mid = left + (right - left) / 2;
73            if (mid >= cols || !hasBlackInCol(mid)) {
74                firstTrueIndex = mid;
75                right = mid - 1;
76            } else {
77                left = mid + 1;
78            }
79        }
80        int rightCol = firstTrueIndex != -1 ? firstTrueIndex - 1 : cols - 1;
81
82        // Calculate the area of the bounding rectangle
83        return (bottomRow - topRow + 1) * (rightCol - leftCol + 1);
84    }
85};
86
1function minArea(image: string[][], x: number, y: number): number {
2    const rows = image.length;
3    const cols = image[0].length;
4
5    const hasBlackInRow = (row: number): boolean => {
6        for (let c = 0; c < cols; c++) {
7            if (image[row][c] === '1') return true;
8        }
9        return false;
10    };
11
12    const hasBlackInCol = (col: number): boolean => {
13        for (let r = 0; r < rows; r++) {
14            if (image[r][col] === '1') return true;
15        }
16        return false;
17    };
18
19    // Find topmost row with black pixel (first true in feasible pattern)
20    let left = 0;
21    let right = x;
22    let firstTrueIndex = -1;
23    while (left <= right) {
24        const mid = Math.floor((left + right) / 2);
25        if (hasBlackInRow(mid)) {
26            firstTrueIndex = mid;
27            right = mid - 1;
28        } else {
29            left = mid + 1;
30        }
31    }
32    const topRow = firstTrueIndex;
33
34    // Find bottommost row with black pixel
35    // Search for first row WITHOUT black pixel, then subtract 1
36    left = x + 1;
37    right = rows;
38    firstTrueIndex = -1;
39    while (left <= right) {
40        const mid = Math.floor((left + right) / 2);
41        if (mid >= rows || !hasBlackInRow(mid)) {
42            firstTrueIndex = mid;
43            right = mid - 1;
44        } else {
45            left = mid + 1;
46        }
47    }
48    const bottomRow = firstTrueIndex !== -1 ? firstTrueIndex - 1 : rows - 1;
49
50    // Find leftmost column with black pixel (first true in feasible pattern)
51    left = 0;
52    right = y;
53    firstTrueIndex = -1;
54    while (left <= right) {
55        const mid = Math.floor((left + right) / 2);
56        if (hasBlackInCol(mid)) {
57            firstTrueIndex = mid;
58            right = mid - 1;
59        } else {
60            left = mid + 1;
61        }
62    }
63    const leftCol = firstTrueIndex;
64
65    // Find rightmost column with black pixel
66    // Search for first column WITHOUT black pixel, then subtract 1
67    left = y + 1;
68    right = cols;
69    firstTrueIndex = -1;
70    while (left <= right) {
71        const mid = Math.floor((left + right) / 2);
72        if (mid >= cols || !hasBlackInCol(mid)) {
73            firstTrueIndex = mid;
74            right = mid - 1;
75        } else {
76            left = mid + 1;
77        }
78    }
79    const rightCol = firstTrueIndex !== -1 ? firstTrueIndex - 1 : cols - 1;
80
81    // Calculate the area of the bounding rectangle
82    return (bottomRow - topRow + 1) * (rightCol - leftCol + 1);
83}
84

Solution Approach

The implementation uses binary search to find the four boundaries of the black pixel region. We apply the standard binary search template consistently for all four searches.

The Binary Search Template:

left, right = min_val, max_val
first_true_index = -1

while left <= right:
    mid = (left + right) // 2
    if feasible(mid):
        first_true_index = mid
        right = mid - 1
    else:
        left = mid + 1

return first_true_index

Finding the Upper Boundary (topmost row with black pixel):

We search for the first row that contains a black pixel. The feasibility pattern is:

  • feasible(row) = True if row contains at least one black pixel
  • Pattern: [F, F, ..., F, T, T, ..., T] where T starts at the topmost row with a black pixel
left, right = 0, x
first_true_index = -1
while left <= right:
    mid = (left + right) // 2
    if has_black_pixel_in_row(mid):
        first_true_index = mid
        right = mid - 1  # Look for earlier row
    else:
        left = mid + 1
top_boundary = first_true_index

Finding the Lower Boundary (bottommost row with black pixel):

We search for the first row (from bottom) that does NOT contain a black pixel, then return the row before it. Alternatively, we redefine feasible:

  • feasible(row) = True if row does NOT contain any black pixel
  • We search in range [x+1, m] for the first empty row, then lower_boundary = first_true_index - 1
left, right = x + 1, rows
first_true_index = -1
while left <= right:
    mid = (left + right) // 2
    if not has_black_pixel_in_row(mid):  # First row with no black pixels
        first_true_index = mid
        right = mid - 1
    else:
        left = mid + 1
bottom_boundary = first_true_index - 1 if first_true_index != -1 else rows - 1

Finding the Left Boundary (leftmost column with black pixel):

Same pattern as upper boundary, but searching columns:

  • feasible(col) = True if column contains at least one black pixel
left, right = 0, y
first_true_index = -1
while left <= right:
    mid = (left + right) // 2
    if has_black_pixel_in_col(mid):
        first_true_index = mid
        right = mid - 1
    else:
        left = mid + 1
left_boundary = first_true_index

Finding the Right Boundary (rightmost column with black pixel):

Search for first column with no black pixels, starting from y+1:

left, right = y + 1, cols
first_true_index = -1
while left <= right:
    mid = (left + right) // 2
    if not has_black_pixel_in_col(mid):
        first_true_index = mid
        right = mid - 1
    else:
        left = mid + 1
right_boundary = first_true_index - 1 if first_true_index != -1 else cols - 1

Calculating the Area:

return (bottom_boundary - top_boundary + 1) * (right_boundary - left_boundary + 1)
  • Height: bottom_boundary - top_boundary + 1 (inclusive of both boundaries)
  • Width: right_boundary - left_boundary + 1 (inclusive of both boundaries)

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 a concrete example using the binary search template to find the bounding rectangle.

Consider this 5×7 matrix with a connected black pixel region:

    0 1 2 3 4 5 6
0   0 0 0 0 0 0 0
1   0 0 1 1 1 0 0
2   0 1 1 1 0 0 0
3   0 0 1 0 0 0 0
4   0 0 0 0 0 0 0

Given: Starting black pixel at position (x=2, y=3) (row 2, column 3). Matrix has 5 rows (m=5) and 7 columns (n=7).

Step 1: Find Upper Boundary (topmost row with black pixel)

  • Feasible condition: row contains a black pixel
  • Search range: [0, 2], first_true_index = -1
  • Iteration 1: left=0, right=2, mid = 1
    • Row 1 has black pixel → feasible!
    • first_true_index = 1, right = 0
  • Iteration 2: left=0, right=0, mid = 0
    • Row 0 has no black pixel → not feasible
    • left = 1
  • Loop ends (left > right): first_true_index = 1
  • Result: top_boundary = 1

Step 2: Find Lower Boundary (bottommost row with black pixel)

  • Feasible condition: row does NOT contain a black pixel (inverted)
  • Search range: [3, 5] (from x+1 to rows), first_true_index = -1
  • Iteration 1: left=3, right=5, mid = 4
    • Row 4 has no black pixel → feasible!
    • first_true_index = 4, right = 3
  • Iteration 2: left=3, right=3, mid = 3
    • Row 3 has black pixel → not feasible
    • left = 4
  • Loop ends: first_true_index = 4
  • Result: bottom_boundary = 4 - 1 = 3

Step 3: Find Left Boundary (leftmost column with black pixel)

  • Feasible condition: column contains a black pixel
  • Search range: [0, 3], first_true_index = -1
  • Iteration 1: left=0, right=3, mid = 1
    • Column 1 has black pixel → feasible!
    • first_true_index = 1, right = 0
  • Iteration 2: left=0, right=0, mid = 0
    • Column 0 has no black pixel → not feasible
    • left = 1
  • Loop ends: first_true_index = 1
  • Result: left_boundary = 1

Step 4: Find Right Boundary (rightmost column with black pixel)

  • Feasible condition: column does NOT contain a black pixel (inverted)
  • Search range: [4, 7] (from y+1 to cols), first_true_index = -1
  • Iteration 1: left=4, right=7, mid = 5
    • Column 5 has no black pixel → feasible!
    • first_true_index = 5, right = 4
  • Iteration 2: left=4, right=4, mid = 4
    • Column 4 has black pixel → not feasible
    • left = 5
  • Loop ends: first_true_index = 5
  • Result: right_boundary = 5 - 1 = 4

Step 5: Calculate Area

  • Height = bottom_boundary - top_boundary + 1 = 3 - 1 + 1 = 3
  • Width = right_boundary - left_boundary + 1 = 4 - 1 + 1 = 4
  • Area = 3 × 4 = 12

The bounding rectangle encompasses rows [1,3] and columns [1,4]:

    0 1 2 3 4 5 6
0   0 0 0 0 0 0 0
1   0 [1 1 1 1] 0 0
2   0 [1 1 1 0] 0 0
3   0 [0 1 0 0] 0 0
4   0 0 0 0 0 0 0

Template Compliance:

  • ✅ Uses first_true_index = -1 to track answer
  • ✅ Uses while left <= right
  • ✅ Uses right = mid - 1 when feasible
  • ✅ Clearly defined feasible conditions for each boundary

Time and Space Complexity

Time Complexity: O(m * log n + n * log m) where m is the number of rows and n is the number of columns in the image.

The algorithm performs 4 binary searches:

  • Two binary searches on rows (finding upper and lower boundaries): Each binary search takes O(log m) iterations, and in each iteration, we scan through all n columns to check if there's a '1' in that row, taking O(n) time. Total: 2 * O(log m * n) = O(n * log m)
  • Two binary searches on columns (finding left and right boundaries): Each binary search takes O(log n) iterations, and in each iteration, we scan through all m rows to check if there's a '1' in that column, taking O(m) time. Total: 2 * O(log n * m) = O(m * log n)

Combined time complexity: O(m * log n + n * log m)

Space Complexity: O(1)

The algorithm only uses a constant amount of extra space for variables (left, right, mid, u, d, l, r, c) regardless of the input size. No additional data structures are created that scale with the input dimensions.

Common Pitfalls

1. Using the Wrong Binary Search Template Variant

The Pitfall: Using while left < right with right = mid is a different binary search variant that doesn't match our standard template. This leads to inconsistent code and harder debugging.

The Banned Pattern:

# WRONG - do not use this variant
while left < right:
    mid = (left + right) // 2
    if condition:
        right = mid
    else:
        left = mid + 1
return left

The Solution: Always use the standard template with first_true_index:

# CORRECT - use this template
left, right = 0, max_val
first_true_index = -1
while left <= right:
    mid = (left + right) // 2
    if feasible(mid):
        first_true_index = mid
        right = mid - 1
    else:
        left = mid + 1
return first_true_index

2. Off-by-One Error in Area Calculation

The Pitfall: Calculating the area as (bottom - top) * (right - left) instead of (bottom - top + 1) * (right - left + 1). This mistake comes from forgetting that both boundary positions are inclusive.

Example: If top = 2, bottom = 4, left = 1, right = 3:

  • Wrong: (4 - 2) * (3 - 1) = 2 * 2 = 4
  • Correct: (4 - 2 + 1) * (3 - 1 + 1) = 3 * 3 = 9

3. Forgetting to Handle the first_true_index == -1 Case

The Pitfall: When using the binary search template, if no feasible value exists in the search range, first_true_index remains -1. Forgetting to handle this case can cause incorrect results or array out-of-bounds errors.

The Solution: Always check if first_true_index is -1 before using it:

if first_true_index == -1:
    # Handle the "not found" case appropriately
    return default_value

For this problem, we know a black pixel exists (given in the input), so the minimum boundaries will always find a valid result. For maximum boundaries, we search for the first empty row/column and subtract 1.

4. Not Leveraging the Known Black Pixel Position

The Pitfall: Setting initial search ranges as [0, rows-1] and [0, cols-1] for all boundaries, which wastes the given information that position (x, y) contains a black pixel.

The Solution: Use the known position to optimize search ranges:

  • Top boundary: search in [0, x] (answer must be ≤ x)
  • Bottom boundary: search in [x, rows-1] (answer must be ≥ x)
  • Left boundary: search in [0, y] (answer must be ≤ y)
  • Right boundary: search in [y, cols-1] (answer must be ≥ y)

This reduces the search space and improves efficiency.

Loading...
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which of the following is a good use case for backtracking?


Recommended Readings

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

Load More