Find the Minimum Area to Cover All Ones II
HardPrompt
You are given a 2D binary array grid. You need to find 3 non-overlapping rectangles having non-zero areas with horizontal and vertical sides such that all the 1's in grid lie inside these rectangles.
Return the minimum possible sum of the area of these rectangles.
Note that the rectangles are allowed to touch.
Example 1:
Input: grid = [[1,0,1],[1,1,1]]
Output: 5
Explanation:

- The 1's at
(0, 0)and(1, 0)are covered by a rectangle of area 2. - The 1's at
(0, 2)and(1, 2)are covered by a rectangle of area 2. - The 1 at
(1, 1)is covered by a rectangle of area 1.
Example 2:
Input: grid = [[1,0,1,0],[0,1,0,1]]
Output: 5
Explanation:

- The 1's at
(0, 0)and(0, 2)are covered by a rectangle of area 3. - The 1 at
(1, 1)is covered by a rectangle of area 1. - The 1 at
(1, 3)is covered by a rectangle of area 1.
Constraints:
1 <= grid.length, grid[i].length <= 30grid[i][j]is either 0 or 1.- The input is generated such that there are at least three 1's in
grid.
Approaches
2 approaches with complexity analysis and trade-offs.
This approach exhaustively checks every possible way to partition the grid into three non-overlapping rectangles. There are six fundamental patterns for such a partition: two parallel vertical cuts, two parallel horizontal cuts, and four 'T'-shaped configurations. The algorithm iterates through all possible positions for these cuts. For each potential partition, it calculates the area of the bounding box for the '1's within each of the three resulting rectangular regions. The sum of these three areas is a candidate for the minimum total area. By checking all possibilities, we can find the global minimum.
Algorithm
- Implement a helper function
getArea(r1, c1, r2, c2)that computes the area of the smallest bounding box covering all '1's within the subgrid from(r1, c1)to(r2, c2). This function iterates through all cells in the subgrid. If no '1's are found, it returns 0. - Initialize a variable
minTotalAreato a very large value. - Systematically check all 6 ways to partition a rectangle into three smaller rectangles:
- Two vertical cuts: Iterate through all possible pairs of vertical cut lines,
c1andc2. This creates three vertical rectangular regions. Calculate the area for each region usinggetArea. If all three areas are non-zero, updateminTotalAreawith their sum. - Two horizontal cuts: Similarly, iterate through all pairs of horizontal cut lines,
r1andr2, and do the same. - Four T-shaped cuts: Iterate through a vertical cut
cand a horizontal cutr. These two cuts divide the grid into four quadrants. By combining three of these quadrants in different ways, we can form four distinct T-shaped partitions. For each of these four configurations, calculate the areas of the three rectangles and updateminTotalAreaif they are all non-zero. The four configurations are: a. Left rectangle, top-right rectangle, bottom-right rectangle. b. Right rectangle, top-left rectangle, bottom-left rectangle. c. Top rectangle, bottom-left rectangle, bottom-right rectangle. d. Bottom rectangle, top-left rectangle, top-right rectangle.
- Two vertical cuts: Iterate through all possible pairs of vertical cut lines,
- After checking all possible partitions,
minTotalAreawill hold the minimum possible sum of areas.
Walkthrough
The core of this method is a helper function, getArea(r1, c1, r2, c2), which calculates the minimum bounding box area for all 1s within a specified rectangular subgrid. This function works by iterating through the cells of the subgrid to find the minimum and maximum row and column indices of the 1s.
The main logic then explores all six partitioning schemes:
-
Two Vertical Cuts: We use two nested loops to iterate through all possible positions for two vertical lines,
c1andc2. These lines divide the grid into three vertical strips:cols 0..c1-1,cols c1..c2-1, andcols c2..n-1. We calculate the area for each strip and sum them up. -
Two Horizontal Cuts: This is analogous to the vertical case, but we iterate through two horizontal lines,
r1andr2, to create three horizontal strips. -
T-Shaped Cuts: A T-shaped partition is formed by one main cut that spans the grid, and a second, perpendicular cut that splits one of the two resulting sub-rectangles. We can iterate through all possible intersection points
(r, c)of a horizontal and a vertical line. This gives us four ways to form three rectangles (e.g., one rectangle on the left ofc, and two rectangles on the right ofcseparated byr). We check all four such configurations for each(r, c)pair.
For every partition, we ensure that each of the three rectangles contains at least one '1' (has a non-zero area) before considering its total area as a potential minimum.
class Solution { private int[][] grid; private int m, n; public int minimumArea(int[][] grid) { this.grid = grid; this.m = grid.length; this.n = grid[0].length; long minArea = Long.MAX_VALUE; // Case 1: Two vertical cuts for (int c1 = 1; c1 < n; c1++) { for (int c2 = c1 + 1; c2 < n; c2++) { long area1 = getArea(0, 0, m - 1, c1 - 1); long area2 = getArea(0, c1, m - 1, c2 - 1); long area3 = getArea(0, c2, m - 1, n - 1); if (area1 > 0 && area2 > 0 && area3 > 0) { minArea = Math.min(minArea, area1 + area2 + area3); } } } // Case 2: Two horizontal cuts for (int r1 = 1; r1 < m; r1++) { for (int r2 = r1 + 1; r2 < m; r2++) { long area1 = getArea(0, 0, r1 - 1, n - 1); long area2 = getArea(r1, 0, r2 - 1, n - 1); long area3 = getArea(r2, 0, m - 1, n - 1); if (area1 > 0 && area2 > 0 && area3 > 0) { minArea = Math.min(minArea, area1 + area2 + area3); } } } // Cases 3-6: T-shaped cuts for (int c = 1; c < n; c++) { for (int r = 1; r < m; r++) { // Config 1: Left, Top-Right, Bottom-Right long a1 = getArea(0, 0, m - 1, c - 1); long a2 = getArea(0, c, r - 1, n - 1); long a3 = getArea(r, c, m - 1, n - 1); if (a1 > 0 && a2 > 0 && a3 > 0) minArea = Math.min(minArea, a1 + a2 + a3); // Config 2: Right, Top-Left, Bottom-Left a1 = getArea(0, c, m - 1, n - 1); a2 = getArea(0, 0, r - 1, c - 1); a3 = getArea(r, 0, m - 1, c - 1); if (a1 > 0 && a2 > 0 && a3 > 0) minArea = Math.min(minArea, a1 + a2 + a3); // Config 3: Top, Bottom-Left, Bottom-Right a1 = getArea(0, 0, r - 1, n - 1); a2 = getArea(r, 0, m - 1, c - 1); a3 = getArea(r, c, m - 1, n - 1); if (a1 > 0 && a2 > 0 && a3 > 0) minArea = Math.min(minArea, a1 + a2 + a3); // Config 4: Bottom, Top-Left, Top-Right a1 = getArea(r, 0, m - 1, n - 1); a2 = getArea(0, 0, r - 1, c - 1); a3 = getArea(0, c, r - 1, n - 1); if (a1 > 0 && a2 > 0 && a3 > 0) minArea = Math.min(minArea, a1 + a2 + a3); } } return (int) minArea; } private long getArea(int r1, int c1, int r2, int c2) { int minR = m, maxR = -1, minC = n, maxC = -1; boolean foundOne = false; for (int i = r1; i <= r2; i++) { for (int j = c1; j <= c2; j++) { if (grid[i][j] == 1) { foundOne = true; minR = Math.min(minR, i); maxR = Math.max(maxR, i); minC = Math.min(minC, j); maxC = Math.max(maxC, j); } } } if (!foundOne) return 0; return (long) (maxR - minR + 1) * (maxC - minC + 1); }}Complexity
Time
O(m^2 * n^2). The dominant part is handling the T-shaped cuts. There are O(m*n) possible cut intersections. For each, we call `getArea` three times. Each `getArea` call can take up to O(m*n) time in the worst case. This leads to a total complexity of O(m*n * m*n) = O(m^2 * n^2). The parallel cut cases have complexities of O(m*n^3) and O(n*m^3), which are lower for square-like grids.
Space
O(1) besides the input grid storage.
Trade-offs
Pros
Conceptually simple and easy to understand.
Guaranteed to find the correct answer because it checks all possible partition structures.
Cons
The time complexity is high due to redundant calculations.
The
getAreafunction is called repeatedly for overlapping subgrids, leading to inefficiency.
This approach significantly optimizes the brute-force method by avoiding redundant calculations through precomputation and dynamic programming-like techniques. We first precompute the bounding box areas for all possible contiguous vertical and horizontal strips of the grid. This allows for O(1) area lookups for the parallel cut partitions. For the more complex T-shaped partitions, we iterate through the primary cut line. For each primary cut, instead of naively calculating the areas of the two smaller rectangles, we use an efficient method to find the optimal secondary cut, reducing the complexity for these cases as well.
Algorithm
- Precomputation:
- Create a table
areaCol[c1][c2]to store the bounding box area for all '1's in the vertical strip from columnc1toc2. This can be computed in O(m*n + n^2) time. - Similarly, create
areaRow[r1][r2]for horizontal strips in O(m*n + m^2) time.
- Create a table
- Initialize
minTotalAreato a very large value. - Case 1 & 2: Parallel cuts.
- For two vertical cuts, iterate through
c1andc2(O(n^2) pairs). The total area isareaCol[0][c1-1] + areaCol[c1][c2-1] + areaCol[c2][n-1]. This is an O(1) lookup. UpdateminTotalArea. - Do the same for two horizontal cuts using
areaRow(O(m^2) iterations).
- For two vertical cuts, iterate through
- Case 3-6: T-shaped cuts.
- Consider the case with a primary vertical cut at
cand a secondary horizontal cut atr(e.g., Left + Top-Right + Bottom-Right). - Iterate through the primary cut
cfrom1ton-1. - The area of the first rectangle (e.g., the left part) is
areaCol[0][c-1], which is a precomputed value. - For the remaining part (e.g., the right part), we need to find the best horizontal split
r. Instead of recomputing from scratch, we can efficiently calculate the areas of the top-right and bottom-right parts for all possibler's in O(m*n) time for a fixedc. - For a fixed
c, precomputetopRightArea[r](area ofgrid[0..r-1][c..n-1]) andbottomRightArea[r](area ofgrid[r..m-1][c..n-1]) for allr. - Then, iterate
rfrom1tom-1to find the minimum sumtopRightArea[r] + bottomRightArea[r]. - Combine this minimum with the area of the first rectangle to update
minTotalArea. - Apply this optimized logic to all four T-shaped cut configurations.
- Consider the case with a primary vertical cut at
- Return
minTotalArea.
Walkthrough
The main idea is to reduce the time spent inside the loops by pre-calculating necessary values.
Precomputation:
First, we compute areaCol[c1][c2], the bounding box area for columns c1 to c2. We can do this in O(n^2) time after an initial O(m*n) scan to find the vertical extents of '1's in each column. Similarly, we compute areaRow[r1][r2] in O(m^2) time.
Partition Evaluation:
-
Parallel Cuts: With the precomputed tables, finding the minimum area for two parallel vertical cuts takes just O(n^2) time, as each partition's area is a sum of three O(1) table lookups. Similarly, horizontal cuts take O(m^2).
-
T-Shaped Cuts: Let's analyze one configuration: a vertical cut at
cseparating a left rectangle from a right part, which is then split horizontally atr. We iteratecfrom1ton-1. The area of the left rectangle is simplyareaCol[0][c-1]. For the right part (grid[:, c:]), we need to find the best horizontal splitr. We can do this efficiently. For the fixedc, we can compute an arraytopRightArea[r](area forgrid[0..r-1][c..n-1]) for allrin O(mn) time by incrementally building the bounding box. Similarly, we computebottomRightArea[r](area forgrid[r..m-1][c..n-1]). Then, we iterate throughrin O(m) to find the minimum sumtopRightArea[r] + bottomRightArea[r]. The total time for this T-cut configuration becomes O(n * (mn + m)) = O(m*n^2). We apply the same logic to all T-cut cases.
This strategy reduces the overall complexity from O(m^2*n^2) to O(mn(m+n)), which is a significant improvement.
class Solution { public int minimumArea(int[][] grid) { int m = grid.length; int n = grid[0].length; long minArea = Long.MAX_VALUE; // Calculate areas for all vertical and horizontal strips long[][] areaCol = precomputeArea(grid, m, n, true); long[][] areaRow = precomputeArea(grid, m, n, false); // Case 1: Two vertical cuts for (int c1 = 1; c1 < n; c1++) { for (int c2 = c1 + 1; c2 < n; c2++) { long a1 = areaCol[0][c1 - 1]; long a2 = areaCol[c1][c2 - 1]; long a3 = areaCol[c2][n - 1]; if (a1 > 0 && a2 > 0 && a3 > 0) { minArea = Math.min(minArea, a1 + a2 + a3); } } } // Case 2: Two horizontal cuts for (int r1 = 1; r1 < m; r1++) { for (int r2 = r1 + 1; r2 < m; r2++) { long a1 = areaRow[0][r1 - 1]; long a2 = areaRow[r1][r2 - 1]; long a3 = areaRow[r2][m - 1]; if (a1 > 0 && a2 > 0 && a3 > 0) { minArea = Math.min(minArea, a1 + a2 + a3); } } } // Cases 3-6: T-shaped cuts for (int c = 1; c < n; c++) { long leftArea = areaCol[0][c - 1]; long rightArea = areaCol[c][n - 1]; long vSplitRight = minAreaFor2Rects(grid, 0, c, m - 1, n - 1, true); long vSplitLeft = minAreaFor2Rects(grid, 0, 0, m - 1, c - 1, true); if (leftArea > 0 && vSplitRight > 0) minArea = Math.min(minArea, leftArea + vSplitRight); if (rightArea > 0 && vSplitLeft > 0) minArea = Math.min(minArea, rightArea + vSplitLeft); } for (int r = 1; r < m; r++) { long topArea = areaRow[0][r - 1]; long bottomArea = areaRow[r][m - 1]; long hSplitBottom = minAreaFor2Rects(grid, r, 0, m - 1, n - 1, false); long hSplitTop = minAreaFor2Rects(grid, 0, 0, r - 1, n - 1, false); if (topArea > 0 && hSplitBottom > 0) minArea = Math.min(minArea, topArea + hSplitBottom); if (bottomArea > 0 && hSplitTop > 0) minArea = Math.min(minArea, bottomArea + hSplitTop); } return (int) minArea; } private long[][] precomputeArea(int[][] grid, int m, int n, boolean isVertical) { int dim1 = isVertical ? n : m; int dim2 = isVertical ? m : n; long[][] area = new long[dim1][dim1]; for (int i = 0; i < dim1; i++) { int minR = dim2, maxR = -1, minC = dim1, maxC = -1; boolean hasOne = false; for (int j = i; j < dim1; j++) { int currentMinR = dim2, currentMaxR = -1; for (int k = 0; k < dim2; k++) { if ((isVertical && grid[k][j] == 1) || (!isVertical && grid[j][k] == 1)) { currentMinR = Math.min(currentMinR, k); currentMaxR = Math.max(currentMaxR, k); } } if (currentMaxR != -1) { hasOne = true; minR = Math.min(minR, currentMinR); maxR = Math.max(maxR, currentMaxR); } if (hasOne) { area[i][j] = (long)(maxR - minR + 1) * (j - i + 1); } } } return area; } private long minAreaFor2Rects(int[][] grid, int r1, int c1, int r2, int c2, boolean verticalSplit) { long minArea = Long.MAX_VALUE; if (verticalSplit) { // Split horizontally for (int r = r1 + 1; r <= r2; r++) { long a1 = getArea(grid, r1, c1, r - 1, c2); long a2 = getArea(grid, r, c1, r2, c2); if (a1 > 0 && a2 > 0) minArea = Math.min(minArea, a1 + a2); } } else { // Split vertically for (int c = c1 + 1; c <= c2; c++) { long a1 = getArea(grid, r1, c1, r2, c - 1); long a2 = getArea(grid, r1, c, r2, c2); if (a1 > 0 && a2 > 0) minArea = Math.min(minArea, a1 + a2); } } return minArea == Long.MAX_VALUE ? 0 : minArea; } private long getArea(int[][] grid, int r1, int c1, int r2, int c2) { int minR = grid.length, maxR = -1, minC = grid[0].length, maxC = -1; boolean foundOne = false; for (int i = r1; i <= r2; i++) { for (int j = c1; j <= c2; j++) { if (grid[i][j] == 1) { foundOne = true; minR = Math.min(minR, i); maxR = Math.max(maxR, i); minC = Math.min(minC, j); maxC = Math.max(maxC, j); } } } if (!foundOne) return 0; return (long)(maxR - minR + 1) * (maxC - minC + 1); }}Complexity
Time
O(mn(m+n)). Precomputation takes O(m*n^2 + n*m^2). The parallel cut checks take O(m^2 + n^2). The T-cut checks take O(n * (m*n)) for vertical primary cuts and O(m * (m*n)) for horizontal primary cuts. The dominant part is the T-cut evaluation, leading to a total complexity of O(m*n^2 + n*m^2).
Space
O(m^2 + n^2) to store the precomputed area tables for horizontal and vertical strips.
Trade-offs
Pros
Much more efficient than the brute-force approach.
Handles the given constraints (m, n <= 30) very quickly.
Cons
More complex to implement due to precomputation steps and more intricate loop structures.
Requires additional space for the precomputed tables.
Solutions
Solution
class Solution {private final int inf = 1 << 30;private int[][] grid;public int minimumSum(int[][] grid) { this.grid = grid; int m = grid.length; int n = grid[0].length; int ans = m * n; for (int i1 = 0; i1 < m - 1; i1++) { for (int i2 = i1 + 1; i2 < m - 1; i2++) { ans = Math.min(ans, f(0, 0, i1, n - 1) + f(i1 + 1, 0, i2, n - 1) + f(i2 + 1, 0, m - 1, n - 1)); } } for (int j1 = 0; j1 < n - 1; j1++) { for (int j2 = j1 + 1; j2 < n - 1; j2++) { ans = Math.min(ans, f(0, 0, m - 1, j1) + f(0, j1 + 1, m - 1, j2) + f(0, j2 + 1, m - 1, n - 1)); } } for (int i = 0; i < m - 1; i++) { for (int j = 0; j < n - 1; j++) { ans = Math.min(ans, f(0, 0, i, j) + f(0, j + 1, i, n - 1) + f(i + 1, 0, m - 1, n - 1)); ans = Math.min(ans, f(0, 0, i, n - 1) + f(i + 1, 0, m - 1, j) + f(i + 1, j + 1, m - 1, n - 1)); ans = Math.min(ans, f(0, 0, i, j) + f(i + 1, 0, m - 1, j) + f(0, j + 1, m - 1, n - 1)); ans = Math.min(ans, f(0, 0, m - 1, j) + f(0, j + 1, i, n - 1) + f(i + 1, j + 1, m - 1, n - 1)); } } return ans; }private int f(int i1, int j1, int i2, int j2) { int x1 = inf, y1 = inf; int x2 = -inf, y2 = -inf; for (int i = i1; i <= i2; i++) { for (int j = j1; j <= j2; j++) { if (grid[i][j] == 1) { x1 = Math.min(x1, i); y1 = Math.min(y1, j); x2 = Math.max(x2, i); y2 = Math.max(y2, j); } } } return (x2 - x1 + 1) * (y2 - y1 + 1); }}class Solution {public: int minimumSum(vector<vector<int>> &grid) { int m = grid.size(); int n = grid[0].size(); int ans = m * n; int inf = INT_MAX / 4; auto f = [&](int i1, int j1, int i2, int j2) { int x1 = inf, y1 = inf; int x2 = -inf, y2 = -inf; for (int i = i1; i <= i2; i++) { for (int j = j1; j <= j2; j++) { if (grid[i][j] == 1) { x1 = min(x1, i); y1 = min(y1, j); x2 = max(x2, i); y2 = max(y2, j); } } } return x1 > x2 || y1 > y2 ? inf : (x2 - x1 + 1) * (y2 - y1 + 1); }; for (int i1 = 0; i1 < m - 1; i1++) { for (int i2 = i1 + 1; i2 < m - 1; i2++) { ans = min(ans, f(0, 0, i1, n - 1) + f(i1 + 1, 0, i2, n - 1) + f(i2 + 1, 0, m - 1, n - 1)); } } for (int j1 = 0; j1 < n - 1; j1++) { for (int j2 = j1 + 1; j2 < n - 1; j2++) { ans = min(ans, f(0, 0, m - 1, j1) + f(0, j1 + 1, m - 1, j2) + f(0, j2 + 1, m - 1, n - 1)); } } for (int i = 0; i < m - 1; i++) { for (int j = 0; j < n - 1; j++) { ans = min(ans, f(0, 0, i, j) + f(0, j + 1, i, n - 1) + f(i + 1, 0, m - 1, n - 1)); ans = min(ans, f(0, 0, i, n - 1) + f(i + 1, 0, m - 1, j) + f(i + 1, j + 1, m - 1, n - 1)); ans = min(ans, f(0, 0, i, j) + f(i + 1, 0, m - 1, j) + f(0, j + 1, m - 1, n - 1)); ans = min(ans, f(0, 0, m - 1, j) + f(0, j + 1, i, n - 1) + f(i + 1, j + 1, m - 1, n - 1)); } } return ans; }};class Solution: def minimumSum(self, grid: List[List[int]]) -> int: def f(i1: int, j1: int, i2: int, j2: int) -> int: x1 = y1 = inf x2 = y2 = - inf for i in range(i1, i2 + 1): for j in range(j1, j2 + 1): if grid[i][j] == 1: x1 = min(x1, i) y1 = min(y1, j) x2 = max(x2, i) y2 = max(y2, j) return (x2 - x1 + 1) * (y2 - y1 + 1) m, n = len(grid), len(grid[0]) ans = m * n for i1 in range(m - 1): for i2 in range(i1 + 1, m - 1): ans = min(ans, f(0, 0, i1, n - 1) + f(i1 + 1, 0, i2, n - 1) + f(i2 + 1, 0, m - 1, n - 1), ) for j1 in range(n - 1): for j2 in range(j1 + 1, n - 1): ans = min(ans, f(0, 0, m - 1, j1) + f(0, j1 + 1, m - 1, j2) + f(0, j2 + 1, m - 1, n - 1), ) for i in range(m - 1): for j in range(n - 1): ans = min(ans, f(0, 0, i, j) + f(0, j + 1, i, n - 1) + f(i + 1, 0, m - 1, n - 1), ) ans = min(ans, f(0, 0, i, n - 1) + f(i + 1, 0, m - 1, j) + f(i + 1, j + 1, m - 1, n - 1), ) ans = min(ans, f(0, 0, i, j) + f(i + 1, 0, m - 1, j) + f(0, j + 1, m - 1, n - 1), ) ans = min(ans, f(0, 0, m - 1, j) + f(0, j + 1, i, n - 1) + f(i + 1, j + 1, m - 1, n - 1), ) return ansVideo walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.