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:
- We know there's only one connected black region
- We have a starting black pixel coordinate (x, y)
- 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.
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
xhas 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:
- Upper boundary: Binary search rows
[0, x]to find the smallest row index that contains a black pixel - Lower boundary: Binary search rows
[x, m-1]to find the largest row index that contains a black pixel - Left boundary: Binary search columns
[0, y]to find the smallest column index that contains a black pixel - 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
731class 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}
901class 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};
861function 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}
84Solution 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) = Trueif 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) = Trueif 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) = Trueif 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 EvaluatorExample 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 = -1to track answer - ✅ Uses
while left <= right - ✅ Uses
right = mid - 1when 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 allncolumns to check if there's a '1' in that row, takingO(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 allmrows to check if there's a '1' in that column, takingO(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.
Which of the following is a good use case for backtracking?
Recommended Readings
https assets algo monster cover_photos dfs svg Depth First Search Prereqs Recursion Review problems recursion_intro Trees problems tree_intro With a solid understanding of recursion under our belts we are now ready to tackle one of the most useful techniques in coding interviews Depth First Search DFS As the name suggests
https assets algo monster cover_photos bfs svg Breadth First Search on Trees Hopefully by this time you've drunk enough DFS Kool Aid to understand its immense power and seen enough visualization to create a call stack in your mind Now let me introduce the companion spell Breadth First Search BFS
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
Want a Structured Path to Master System Design Too? Don’t Miss This!