Given a list of axis-aligned rectangles represented as arrays [x1, y1, x2, y2]
, where (x1, y1)
is the bottom-left and (x2, y2)
is the top-right corner, determine if they together form an exact cover of a rectangular region.
Return True
if the rectangles form a perfect rectangle, otherwise False
.
At first glance, you might think to check every pair of rectangles for overlap or gaps, but this quickly becomes inefficient as the number of rectangles increases. Brute-force would mean checking every possible rectangle pair, which is slow and hard to implement.
Instead, let's consider what makes a set of rectangles form a perfect rectangle:
This leads us to a more efficient approach: use area calculation and track all corners using a set or map, focusing on the unique corners of the overall rectangle.
Here’s a step-by-step breakdown of the optimized solution:
x1, y1
(bottom-left) and maximum x2, y2
(top-right). This defines the "perfect" rectangle we expect to form.(x1, y1)
, (x1, y2)
, (x2, y1)
, (x2, y2)
.We use a set for corners because insertion and removal are O(1) operations, making the solution efficient.
Suppose the input rectangles are:
[1,1,3,3]
[3,1,4,2]
[3,2,4,4]
[1,3,2,4]
[2,3,3,4]
Let's trace the solution:
x1=1
, min y1=1
, max x2=4
, max y2=4
. So the bounding rectangle is [1,1,4,4]
.
[1,1,3,3]
: area = 4[3,1,4,2]
: area = 1[3,2,4,4]
: area = 2[1,3,2,4]
: area = 1[2,3,3,4]
: area = 1Total area = 4+1+2+1+1 = 9
Bounding rectangle area = (4-1)*(4-1) = 9
(1,1)
, (1,4)
, (4,1)
, (4,4)
remain in the set.class Solution:
def isRectangleCover(self, rectangles):
min_x = min(rect[0] for rect in rectangles)
min_y = min(rect[1] for rect in rectangles)
max_x = max(rect[2] for rect in rectangles)
max_y = max(rect[3] for rect in rectangles)
area = 0
corners = set()
for x1, y1, x2, y2 in rectangles:
area += (x2 - x1) * (y2 - y1)
for corner in [(x1, y1), (x1, y2), (x2, y1), (x2, y2)]:
if corner in corners:
corners.remove(corner)
else:
corners.add(corner)
expected_area = (max_x - min_x) * (max_y - min_y)
expected_corners = set([
(min_x, min_y), (min_x, max_y),
(max_x, min_y), (max_x, max_y)
])
return area == expected_area and corners == expected_corners
class Solution {
public:
bool isRectangleCover(vector<vector<int>>& rectangles) {
int min_x = INT_MAX, min_y = INT_MAX, max_x = INT_MIN, max_y = INT_MIN;
set<pair<int, int>> corners;
long area = 0;
for (auto& rect : rectangles) {
min_x = min(min_x, rect[0]);
min_y = min(min_y, rect[1]);
max_x = max(max_x, rect[2]);
max_y = max(max_y, rect[3]);
area += (long)(rect[2] - rect[0]) * (rect[3] - rect[1]);
pair<int, int> pts[4] = {
{rect[0], rect[1]}, {rect[0], rect[3]},
{rect[2], rect[1]}, {rect[2], rect[3]}
};
for (auto& p : pts) {
if (corners.count(p)) corners.erase(p);
else corners.insert(p);
}
}
long expected = (long)(max_x - min_x) * (max_y - min_y);
set<pair<int, int>> expected_corners = {
{min_x, min_y}, {min_x, max_y}, {max_x, min_y}, {max_x, max_y}
};
return area == expected && corners == expected_corners;
}
};
class Solution {
public boolean isRectangleCover(int[][] rectangles) {
int minX = Integer.MAX_VALUE, minY = Integer.MAX_VALUE;
int maxX = Integer.MIN_VALUE, maxY = Integer.MIN_VALUE;
Set<String> corners = new HashSet<>();
int area = 0;
for (int[] rect : rectangles) {
minX = Math.min(minX, rect[0]);
minY = Math.min(minY, rect[1]);
maxX = Math.max(maxX, rect[2]);
maxY = Math.max(maxY, rect[3]);
area += (rect[2] - rect[0]) * (rect[3] - rect[1]);
String[] pts = {
rect[0] + " " + rect[1],
rect[0] + " " + rect[3],
rect[2] + " " + rect[1],
rect[2] + " " + rect[3]
};
for (String p : pts) {
if (!corners.add(p)) corners.remove(p);
}
}
int expected = (maxX - minX) * (maxY - minY);
Set<String> expectedCorners = new HashSet<>();
expectedCorners.add(minX + " " + minY);
expectedCorners.add(minX + " " + maxY);
expectedCorners.add(maxX + " " + minY);
expectedCorners.add(maxX + " " + maxY);
return area == expected && corners.equals(expectedCorners);
}
}
var isRectangleCover = function(rectangles) {
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
let area = 0;
let corners = new Set();
for (let rect of rectangles) {
minX = Math.min(minX, rect[0]);
minY = Math.min(minY, rect[1]);
maxX = Math.max(maxX, rect[2]);
maxY = Math.max(maxY, rect[3]);
area += (rect[2] - rect[0]) * (rect[3] - rect[1]);
let pts = [
rect[0] + ',' + rect[1],
rect[0] + ',' + rect[3],
rect[2] + ',' + rect[1],
rect[2] + ',' + rect[3]
];
for (let p of pts) {
if (corners.has(p)) corners.delete(p);
else corners.add(p);
}
}
let expected = (maxX - minX) * (maxY - minY);
let expectedCorners = new Set([
minX + ',' + minY,
minX + ',' + maxY,
maxX + ',' + minY,
maxX + ',' + maxY
]);
if (area !== expected) return false;
if (corners.size !== 4) return false;
for (let p of expectedCorners) {
if (!corners.has(p)) return false;
}
return true;
};
Brute-force approach:
Thus, the optimized solution is highly efficient compared to brute-force.
The key insight is that a perfect rectangle cover requires both area matching and exactly four unique corners (the outermost corners). By tracking areas and using a set to manage corners, we elegantly avoid dealing with complex overlap/gap checks. This approach is efficient, easy to implement, and leverages set operations for correctness.