750. Number Of Corner Rectangles đ
Problem Description
You are given a 2D matrix grid
of size m x n
where each cell contains either 0
or 1
. Your task is to count the number of corner rectangles that can be formed in this grid.
A corner rectangle is defined as a rectangle where:
- All four corners of the rectangle have the value
1
- The rectangle is axis-aligned (its sides are parallel to the grid's rows and columns)
- The four corner cells must be distinct (different positions in the grid)
- Only the corners need to be
1
- the edges and interior can be either0
or1
For example, if you have four 1
s at positions (r1, c1)
, (r1, c2)
, (r2, c1)
, and (r2, c2)
where r1 < r2
and c1 < c2
, these form a valid corner rectangle.
The solution uses a hash table approach where it processes the grid row by row. For each row, it finds all pairs of columns (i, j)
where both positions contain 1
. The hash table keeps track of how many previous rows have 1
s at the same column pair (i, j)
. When processing a new row with 1
s at columns i
and j
, the count in the hash table tells us how many rectangles can be formed with the current row as the bottom edge and one of the previous rows as the top edge. After counting, the current pair is added to the hash table for future rows to reference.
Intuition
To count corner rectangles, we need to find all possible combinations of four 1
s that form rectangle corners. A naive approach would be to check all possible combinations of four cells, but this would be extremely inefficient with time complexity of O(m^2 * n^2)
or worse.
Let's think about what defines a rectangle: we need two rows and two columns. If we fix two rows r1
and r2
, then for these rows to form rectangles, we need to find column pairs (i, j)
where both rows have 1
s at columns i
and j
.
This observation leads to a key insight: instead of looking for four corners at once, we can break down the problem by processing rows sequentially. For any row we're currently examining, we can ask: "How many rectangles can I form where this row is the bottom edge?"
To answer this, we need to know how many previous rows have the same pattern of column pairs with 1
s. If the current row has 1
s at columns i
and j
, and we've seen k
previous rows that also have 1
s at columns i
and j
, then we can form k
rectangles using the current row as the bottom edge (each of those k
previous rows can serve as the top edge).
This naturally suggests using a hash table where the key is a column pair (i, j)
and the value is the count of rows we've seen so far that have 1
s at both columns i
and j
. As we process each row:
- For every pair of
1
s in the current row at columns(i, j)
, we check our hash table to see how many rectangles we can form - We then increment the count for this column pair in our hash table for future rows to use
This approach is efficient because we only iterate through each row once and for each row we only consider pairs of columns that actually contain 1
s, avoiding unnecessary checks.
Learn more about Math and Dynamic Programming patterns.
Solution Approach
The implementation uses a hash table combined with row-by-row enumeration to efficiently count corner rectangles.
Data Structure:
cnt
: ACounter
(hash table) that stores column pairs as keys(i, j)
and their occurrence count across previous rows as valuesans
: Running total of corner rectangles found
Algorithm Steps:
-
Initialize variables:
ans = 0
to store the total count of rectanglescnt = Counter()
to track column pairsn = len(grid[0])
to get the number of columns
-
Process each row sequentially:
for row in grid:
Each row will potentially serve as the bottom edge of rectangles.
-
Find all pairs of
1
s in the current row:for i, c1 in enumerate(row): if c1: # If column i has a 1 for j in range(i + 1, n): if row[j]: # If column j also has a 1
We iterate through all column positions. When we find a
1
at columni
, we look for all1
s at columnsj > i
to form valid column pairs. -
Count rectangles using the current column pair:
ans += cnt[(i, j)]
The value
cnt[(i, j)]
tells us how many previous rows have1
s at both columnsi
andj
. Each such row can form a rectangle with the current row, so we add this count to our answer. -
Update the hash table:
cnt[(i, j)] += 1
After counting, we increment the count for this column pair
(i, j)
in the hash table, as the current row can now be used as a top edge for future rectangles.
Time Complexity: O(m * n^2)
where m
is the number of rows and n
is the number of columns. For each row, we potentially examine all O(n^2)
column pairs.
Space Complexity: O(n^2)
for storing at most n * (n-1) / 2
column pairs in the hash table.
The elegance of this solution lies in its incremental nature - by processing rows one at a time and maintaining a running count of column pair patterns, we avoid redundant computations and achieve an efficient solution.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example to illustrate how the hash table approach counts corner rectangles.
Consider this 4Ă3 grid:
grid = [ [1, 1, 1], # row 0 [1, 0, 1], # row 1 [0, 1, 1], # row 2 [1, 1, 0] # row 3 ]
Initial State:
ans = 0
(rectangle count)cnt = {}
(empty hash table for column pairs)
Processing Row 0: [1, 1, 1]
Find all pairs of 1s:
- Columns (0,1): both have 1s â
ans += cnt[(0,1)] = 0
, thencnt[(0,1)] = 1
- Columns (0,2): both have 1s â
ans += cnt[(0,2)] = 0
, thencnt[(0,2)] = 1
- Columns (1,2): both have 1s â
ans += cnt[(1,2)] = 0
, thencnt[(1,2)] = 1
State after row 0: ans = 0
, cnt = {(0,1): 1, (0,2): 1, (1,2): 1}
Processing Row 1: [1, 0, 1]
Find all pairs of 1s:
- Columns (0,2): both have 1s â
ans += cnt[(0,2)] = 1
(forms 1 rectangle with row 0!), thencnt[(0,2)] = 2
The rectangle formed has corners at: (0,0), (0,2), (1,0), (1,2)
State after row 1: ans = 1
, cnt = {(0,1): 1, (0,2): 2, (1,2): 1}
Processing Row 2: [0, 1, 1]
Find all pairs of 1s:
- Columns (1,2): both have 1s â
ans += cnt[(1,2)] = 1
(forms 1 rectangle with row 0!), thencnt[(1,2)] = 2
The rectangle formed has corners at: (0,1), (0,2), (2,1), (2,2)
State after row 2: ans = 2
, cnt = {(0,1): 1, (0,2): 2, (1,2): 2}
Processing Row 3: [1, 1, 0]
Find all pairs of 1s:
- Columns (0,1): both have 1s â
ans += cnt[(0,1)] = 1
(forms 1 rectangle with row 0!), thencnt[(0,1)] = 2
The rectangle formed has corners at: (0,0), (0,1), (3,0), (3,1)
Final Result: ans = 3
The three corner rectangles found are:
- Corners at (0,0), (0,2), (1,0), (1,2)
- Corners at (0,1), (0,2), (2,1), (2,2)
- Corners at (0,0), (0,1), (3,0), (3,1)
Notice how the hash table efficiently tracks which column pairs have appeared in previous rows, allowing us to instantly count how many rectangles can be formed when we encounter the same column pair in a new row.
Solution Implementation
1class Solution:
2 def countCornerRectangles(self, grid: List[List[int]]) -> int:
3 """
4 Count the number of corner rectangles in a binary grid.
5 A corner rectangle is formed by four 1s that form the corners of a rectangle.
6
7 Args:
8 grid: 2D list of integers (0 or 1)
9
10 Returns:
11 Number of corner rectangles
12 """
13 # Initialize the result counter
14 result = 0
15
16 # Dictionary to store counts of column pairs that have 1s in previous rows
17 # Key: (col_i, col_j) tuple representing two column indices
18 # Value: Number of previous rows where both columns have 1s
19 column_pair_counts = {}
20
21 # Get the number of columns
22 num_columns = len(grid[0])
23
24 # Process each row in the grid
25 for row in grid:
26 # Find all pairs of columns that both have 1s in the current row
27 for col_i in range(num_columns):
28 # Skip if current position is 0
29 if row[col_i] == 0:
30 continue
31
32 # Check all columns to the right of col_i
33 for col_j in range(col_i + 1, num_columns):
34 # Skip if the second position is 0
35 if row[col_j] == 0:
36 continue
37
38 # Column pair (col_i, col_j) both have 1s in current row
39 column_pair = (col_i, col_j)
40
41 # If this column pair had 1s in previous rows,
42 # each previous occurrence forms a rectangle with current row
43 if column_pair in column_pair_counts:
44 result += column_pair_counts[column_pair]
45 column_pair_counts[column_pair] += 1
46 else:
47 # First time seeing this column pair with both 1s
48 column_pair_counts[column_pair] = 1
49
50 return result
51
1class Solution {
2 public int countCornerRectangles(int[][] grid) {
3 int numColumns = grid[0].length;
4 int rectangleCount = 0;
5
6 // Map to store count of column pairs that have 1s in previous rows
7 // Key: pair of column indices [i, j], Value: count of rows where both columns have 1
8 Map<List<Integer>, Integer> columnPairCount = new HashMap<>();
9
10 // Process each row
11 for (int[] currentRow : grid) {
12 // Find all pairs of columns that have 1s in the current row
13 for (int col1 = 0; col1 < numColumns; ++col1) {
14 if (currentRow[col1] == 1) {
15 for (int col2 = col1 + 1; col2 < numColumns; ++col2) {
16 if (currentRow[col2] == 1) {
17 // Create a column pair identifier
18 List<Integer> columnPair = List.of(col1, col2);
19
20 // If this column pair appeared in previous rows,
21 // we can form rectangles with those rows and current row
22 rectangleCount += columnPairCount.getOrDefault(columnPair, 0);
23
24 // Update the count for this column pair
25 columnPairCount.merge(columnPair, 1, Integer::sum);
26 }
27 }
28 }
29 }
30 }
31
32 return rectangleCount;
33 }
34}
35
1class Solution {
2public:
3 int countCornerRectangles(vector<vector<int>>& grid) {
4 int numColumns = grid[0].size();
5 int rectangleCount = 0;
6
7 // Map to store count of pairs of columns that have 1s in previous rows
8 // Key: pair of column indices (col1, col2)
9 // Value: number of rows where both columns have 1s
10 map<pair<int, int>, int> columnPairCount;
11
12 // Process each row of the grid
13 for (const auto& currentRow : grid) {
14 // Find all pairs of columns with 1s in the current row
15 for (int leftCol = 0; leftCol < numColumns; ++leftCol) {
16 if (currentRow[leftCol] == 1) {
17 for (int rightCol = leftCol + 1; rightCol < numColumns; ++rightCol) {
18 if (currentRow[rightCol] == 1) {
19 // If this column pair had 1s in previous rows,
20 // each previous occurrence forms a rectangle with current row
21 rectangleCount += columnPairCount[{leftCol, rightCol}];
22
23 // Increment count for this column pair
24 ++columnPairCount[{leftCol, rightCol}];
25 }
26 }
27 }
28 }
29 }
30
31 return rectangleCount;
32 }
33};
34
1/**
2 * Counts the number of corner rectangles in a binary grid.
3 * A corner rectangle is defined by four 1s that form the corners of an axis-aligned rectangle.
4 *
5 * @param grid - 2D binary array where 1 represents a corner point
6 * @returns The total number of corner rectangles that can be formed
7 */
8function countCornerRectangles(grid: number[][]): number {
9 const columnCount: number = grid[0].length;
10 let totalRectangles: number = 0;
11
12 // Map to store count of column pairs that have 1s in previous rows
13 // Key: encoded column pair (col1 * 200 + col2)
14 // Value: count of rows where both columns have 1s
15 const columnPairCounts: Map<number, number> = new Map<number, number>();
16
17 // Process each row in the grid
18 for (const currentRow of grid) {
19 // Find all pairs of columns with 1s in the current row
20 for (let leftColumn = 0; leftColumn < columnCount; ++leftColumn) {
21 if (currentRow[leftColumn] === 1) {
22 for (let rightColumn = leftColumn + 1; rightColumn < columnCount; ++rightColumn) {
23 if (currentRow[rightColumn] === 1) {
24 // Encode the column pair as a single number
25 const encodedPair: number = leftColumn * 200 + rightColumn;
26
27 // Add rectangles formed with this column pair in previous rows
28 const previousOccurrences: number = columnPairCounts.get(encodedPair) ?? 0;
29 totalRectangles += previousOccurrences;
30
31 // Update count for this column pair
32 columnPairCounts.set(encodedPair, previousOccurrences + 1);
33 }
34 }
35 }
36 }
37 }
38
39 return totalRectangles;
40}
41
Time and Space Complexity
Time Complexity: O(m à n²)
The algorithm iterates through each row of the grid (m
rows total). For each row, it uses two nested loops to examine all pairs of columns where both positions contain 1s. In the worst case where a row contains all 1s, we examine n Ă (n-1) / 2
pairs, which is O(n²)
. Since we do this for all m
rows, the overall time complexity is O(m à n²)
.
Space Complexity: O(n²)
The space is dominated by the cnt
Counter dictionary, which stores counts for column pairs (i, j)
. In the worst case, we could have entries for all possible column pairs. The number of unique column pairs is n Ă (n-1) / 2
, which is O(n²)
. Each entry stores a tuple of two integers as the key and an integer count as the value, but the number of entries determines the space complexity, giving us O(n²)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Column Pair Ordering
A common mistake is not maintaining consistent ordering when creating column pairs as hash table keys. If you accidentally create both (i, j)
and (j, i)
for the same pair of columns, they'll be treated as different keys in the hash table, leading to incorrect counts.
Incorrect Implementation:
# WRONG: Might create both (2, 5) and (5, 2) as separate keys
for col_i in range(num_columns):
if row[col_i] == 1:
for col_j in range(num_columns): # Should start from col_i + 1
if col_i != col_j and row[col_j] == 1:
column_pair = (col_i, col_j) # Might be (5, 2) in one iteration
Correct Implementation:
# RIGHT: Always maintains col_i < col_j ordering
for col_i in range(num_columns):
if row[col_i] == 1:
for col_j in range(col_i + 1, num_columns): # Ensures col_j > col_i
if row[col_j] == 1:
column_pair = (col_i, col_j) # Always (smaller, larger)
2. Counting Rectangles Before Updating the Hash Table
The order of operations matters. You must count rectangles using the current hash table value before incrementing it. Reversing this order will include the current row in its own rectangle count.
Incorrect Implementation:
# WRONG: Updates counter before using it if column_pair in column_pair_counts: column_pair_counts[column_pair] += 1 # Increment first result += column_pair_counts[column_pair] # Then add (wrong count!) else: column_pair_counts[column_pair] = 1
Correct Implementation:
# RIGHT: Uses counter value before updating if column_pair in column_pair_counts: result += column_pair_counts[column_pair] # Add current count first column_pair_counts[column_pair] += 1 # Then increment for future else: column_pair_counts[column_pair] = 1
3. Inefficient Checking for 1s
Processing all column pairs even when one or both positions contain 0 wastes computation time.
Inefficient Implementation:
# INEFFICIENT: Checks all pairs, then filters
for col_i in range(num_columns):
for col_j in range(col_i + 1, num_columns):
if row[col_i] == 1 and row[col_j] == 1: # Check inside nested loop
# Process pair
Efficient Implementation:
# EFFICIENT: Early termination when encountering 0s
for col_i in range(num_columns):
if row[col_i] == 0: # Skip early if first column is 0
continue
for col_j in range(col_i + 1, num_columns):
if row[col_j] == 0: # Skip if second column is 0
continue
# Process pair (both are guaranteed to be 1)
4. Using Mutable Objects as Hash Keys
Using lists instead of tuples for column pairs will cause errors since lists aren't hashable.
Incorrect:
column_pair = [col_i, col_j] # Lists are mutable and unhashable column_pair_counts[column_pair] += 1 # TypeError!
Correct:
column_pair = (col_i, col_j) # Tuples are immutable and hashable column_pair_counts[column_pair] += 1 # Works correctly
What's the relationship between a tree and a graph?
Recommended Readings
Math for Technical Interviews How much math do I need to know for technical interviews The short answer is about high school level math Computer science is often associated with math and some universities even place their computer science department under the math faculty However the reality is that you
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
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
Want a Structured Path to Master System Design Too? Donât Miss This!