Facebook Pixel

391. Perfect Rectangle

HardGeometryArrayHash TableMathLine Sweep
Leetcode Link

Problem Description

You are given an array of rectangles where each rectangle is represented as rectangles[i] = [x_i, y_i, a_i, b_i]. Each rectangle is axis-aligned (parallel to the x and y axes) with:

  • (x_i, y_i) as the bottom-left corner
  • (a_i, b_i) as the top-right corner

The task is to determine if all these rectangles together form an exact cover of a rectangular region. This means:

  1. The rectangles must combine to form a single larger rectangle with no gaps
  2. There should be no overlapping areas between rectangles
  3. Every point inside the larger rectangle should be covered exactly once

The solution uses two key observations to verify if rectangles form an exact cover:

Area Check: The sum of all individual rectangle areas must equal the area of the bounding rectangle (formed by the minimum and maximum coordinates).

Corner Counting:

  • Each corner point of a rectangle is counted based on how many rectangles share that corner
  • For the four corners of the overall bounding rectangle, each should appear exactly once (count = 1)
  • For all internal corners (where rectangles meet), each point should appear an even number of times (2 or 4), since internal corners are shared by adjacent rectangles

The algorithm:

  1. Calculates the total area and finds the bounding rectangle coordinates (minX, minY) to (maxX, maxY)
  2. Counts how many times each corner point appears across all rectangles
  3. Verifies that the total area matches the bounding rectangle area
  4. Checks that the four outer corners appear exactly once
  5. Confirms all internal corners appear 2 or 4 times (valid sharing between rectangles)

If all conditions are met, the rectangles form an exact cover; otherwise, they don't.

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

Intuition

To understand why this solution works, let's think about what happens when rectangles perfectly tile a region.

First, consider the area constraint. If rectangles form an exact cover without gaps or overlaps, the sum of their individual areas must equal the total area of the region they cover. This is straightforward - any gap would mean less area covered, and any overlap would mean counting some area twice.

The clever insight comes from observing corner points. Imagine placing rectangles on a grid and looking at where their corners meet:

  1. Outer corners of the bounding rectangle: These points can only belong to one rectangle. If a corner of the overall region appeared in multiple rectangles, it would mean rectangles are extending beyond the boundary or overlapping at that corner.

  2. Internal points where rectangles meet: When rectangles fit together perfectly like puzzle pieces, their corners meet in specific patterns:

    • When two rectangles share an edge, they meet at a point that appears exactly 2 times (once for each rectangle)
    • When four rectangles meet at a cross-junction (like a '+' sign), that point appears exactly 4 times

Why can't an internal point appear an odd number of times? Picture trying to fit rectangles together where a point appears 3 times - you'd either have a gap (incomplete coverage) or an overlap (double coverage). The geometry simply doesn't allow for perfect tiling with odd corner counts at internal points.

This corner-counting technique effectively detects both overlaps and gaps:

  • Overlapping rectangles would create internal points with counts like 3, 5, or higher odd numbers
  • Gaps would leave some corners appearing only once internally or create mismatched corner counts

By combining the area check (ensures no gaps or overlaps in total) with the corner pattern check (ensures proper local fitting), we can definitively determine if the rectangles form a perfect cover.

Solution Approach

The implementation follows a systematic approach to verify both the area constraint and corner point patterns:

1. Initialize tracking variables:

  • area: Accumulates the total area of all rectangles
  • minX, minY, maxX, maxY: Track the bounding rectangle coordinates
  • cnt: A dictionary to count occurrences of each corner point

2. Process each rectangle: For each rectangle [x, y, a, b] in the input:

  • Add its area (a - x) * (b - y) to the total area
  • Update the bounding box by comparing with current min/max values:
    • minX = min(minX, x), minY = min(minY, y)
    • maxX = max(maxX, a), maxY = max(maxY, b)
  • Count all four corners of this rectangle in the dictionary:
    • Bottom-left: (x, y)
    • Top-left: (x, b)
    • Top-right: (a, b)
    • Bottom-right: (a, y)

3. Verify the area condition: Check if the sum of individual areas equals the bounding rectangle area:

area == (maxX - minX) * (maxY - minY)

4. Verify the four outer corners: The four corners of the bounding rectangle must each appear exactly once:

  • cnt[(minX, minY)] == 1 (bottom-left)
  • cnt[(minX, maxY)] == 1 (top-left)
  • cnt[(maxX, maxY)] == 1 (top-right)
  • cnt[(maxX, minY)] == 1 (bottom-right)

If any of these conditions fail, return False.

5. Check internal corners: After removing the four outer corners from the dictionary:

del cnt[(minX, minY)], cnt[(minX, maxY)], cnt[(maxX, maxY)], cnt[(maxX, minY)]

All remaining points (internal corners) must have counts of either 2 or 4:

all(c == 2 or c == 4 for c in cnt.values())

The algorithm runs in O(n) time where n is the number of rectangles, as we iterate through the list once and perform constant-time operations for each rectangle. The space complexity is also O(n) for storing the corner points in the dictionary.

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 small example where we have 3 rectangles that form an exact cover:

  • Rectangle 1: [1, 1, 3, 3] (bottom-left at (1,1), top-right at (3,3))
  • Rectangle 2: [3, 1, 4, 2] (bottom-left at (3,1), top-right at (4,2))
  • Rectangle 3: [3, 2, 4, 3] (bottom-left at (3,2), top-right at (4,3))

Step 1: Process each rectangle and track information

For Rectangle 1 [1, 1, 3, 3]:

  • Area = (3-1) × (3-1) = 4
  • Update bounds: minX=1, minY=1, maxX=3, maxY=3
  • Count corners: (1,1)→1, (1,3)→1, (3,3)→1, (3,1)→1

For Rectangle 2 [3, 1, 4, 2]:

  • Area total = 4 + (4-3) × (2-1) = 4 + 1 = 5
  • Update bounds: minX=1, minY=1, maxX=4, maxY=3
  • Count corners: (3,1)→2, (3,2)→1, (4,2)→1, (4,1)→1

For Rectangle 3 [3, 2, 4, 3]:

  • Area total = 5 + (4-3) × (3-2) = 5 + 1 = 6
  • Update bounds: minX=1, minY=1, maxX=4, maxY=3
  • Count corners: (3,2)→2, (3,3)→2, (4,3)→1, (4,2)→2

Step 2: Verify area condition

  • Total area of rectangles: 6
  • Bounding rectangle area: (4-1) × (3-1) = 3 × 2 = 6 ✓

Step 3: Check corner counts Our corner count dictionary contains:

  • (1,1)→1, (1,3)→1, (4,3)→1, (4,1)→1 (outer corners)
  • (3,1)→2, (3,2)→2, (3,3)→2, (4,2)→2 (internal corners)

Step 4: Verify outer corners The four corners of the bounding rectangle all appear exactly once:

  • (1,1): count = 1 ✓
  • (1,3): count = 1 ✓
  • (4,3): count = 1 ✓
  • (4,1): count = 1 ✓

Step 5: Verify internal corners After removing outer corners, remaining corners all have count 2:

  • (3,1): count = 2 ✓ (where Rectangle 1 and 2 meet)
  • (3,2): count = 2 ✓ (where Rectangle 2 and 3 meet)
  • (3,3): count = 2 ✓ (where Rectangle 1 and 3 meet)
  • (4,2): count = 2 ✓ (where Rectangle 2 and 3 meet)

All conditions are satisfied, so these rectangles form an exact cover!

Visualized:

3 |[1]|[3]|
2 |[1]|[2]|
1 |[1]|[2]|
  +---------
    1 2 3 4

Where [1], [2], [3] represent the three rectangles perfectly tiling the region.

Solution Implementation

1class Solution:
2    def isRectangleCover(self, rectangles: List[List[int]]) -> bool:
3        """
4        Determines if given rectangles form an exact cover of a rectangular region.
5      
6        The algorithm checks two conditions:
7        1. Total area of all rectangles equals the area of the bounding rectangle
8        2. Corner points appear with correct frequencies:
9           - The 4 corners of the bounding rectangle appear exactly once
10           - All other corners appear 2 or 4 times (shared by adjacent rectangles)
11      
12        Args:
13            rectangles: List of rectangles, each as [x1, y1, x2, y2]
14      
15        Returns:
16            True if rectangles form an exact cover, False otherwise
17        """
18        from collections import defaultdict
19      
20        # Initialize variables to track total area and bounding box
21        total_area = 0
22        min_x, min_y = rectangles[0][0], rectangles[0][1]
23        max_x, max_y = rectangles[0][2], rectangles[0][3]
24      
25        # Dictionary to count occurrences of each corner point
26        corner_count = defaultdict(int)
27      
28        # Process each rectangle
29        for rectangle in rectangles:
30            x1, y1, x2, y2 = rectangle
31          
32            # Add rectangle's area to total
33            total_area += (x2 - x1) * (y2 - y1)
34          
35            # Update bounding box coordinates
36            min_x = min(min_x, x1)
37            min_y = min(min_y, y1)
38            max_x = max(max_x, x2)
39            max_y = max(max_y, y2)
40          
41            # Count occurrences of each corner of current rectangle
42            corner_count[(x1, y1)] += 1  # Bottom-left corner
43            corner_count[(x1, y2)] += 1  # Top-left corner
44            corner_count[(x2, y2)] += 1  # Top-right corner
45            corner_count[(x2, y1)] += 1  # Bottom-right corner
46      
47        # Check if total area matches bounding box area
48        # and if the four corners of bounding box appear exactly once
49        if (total_area != (max_x - min_x) * (max_y - min_y) or
50            corner_count[(min_x, min_y)] != 1 or
51            corner_count[(min_x, max_y)] != 1 or
52            corner_count[(max_x, max_y)] != 1 or
53            corner_count[(max_x, min_y)] != 1):
54            return False
55      
56        # Remove the four outer corners from consideration
57        del corner_count[(min_x, min_y)]
58        del corner_count[(min_x, max_y)]
59        del corner_count[(max_x, max_y)]
60        del corner_count[(max_x, min_y)]
61      
62        # Check that all internal corners appear 2 or 4 times
63        # 2 times: corner shared by 2 rectangles (edge point)
64        # 4 times: corner shared by 4 rectangles (internal point)
65        return all(count == 2 or count == 4 for count in corner_count.values())
66
1class Solution {
2    public boolean isRectangleCover(int[][] rectangles) {
3        // Track total area of all rectangles
4        long totalArea = 0;
5      
6        // Track the boundaries of the overall rectangle
7        int minX = rectangles[0][0];
8        int minY = rectangles[0][1];
9        int maxX = rectangles[0][2];
10        int maxY = rectangles[0][3];
11      
12        // Map to count occurrences of each corner point
13        Map<Point, Integer> cornerCount = new HashMap<>();
14
15        // Process each rectangle
16        for (int[] rectangle : rectangles) {
17            // Calculate and add area of current rectangle
18            int width = rectangle[2] - rectangle[0];
19            int height = rectangle[3] - rectangle[1];
20            totalArea += (long) width * height;
21
22            // Update overall boundaries
23            minX = Math.min(minX, rectangle[0]);
24            minY = Math.min(minY, rectangle[1]);
25            maxX = Math.max(maxX, rectangle[2]);
26            maxY = Math.max(maxY, rectangle[3]);
27
28            // Count occurrences of each corner of the current rectangle
29            // Bottom-left corner
30            cornerCount.merge(new Point(rectangle[0], rectangle[1]), 1, Integer::sum);
31            // Top-left corner
32            cornerCount.merge(new Point(rectangle[0], rectangle[3]), 1, Integer::sum);
33            // Top-right corner
34            cornerCount.merge(new Point(rectangle[2], rectangle[3]), 1, Integer::sum);
35            // Bottom-right corner
36            cornerCount.merge(new Point(rectangle[2], rectangle[1]), 1, Integer::sum);
37        }
38
39        // Calculate expected area of the overall rectangle
40        long expectedArea = (long) (maxX - minX) * (maxY - minY);
41      
42        // Check if total area matches and if the four corners of the overall rectangle appear exactly once
43        if (totalArea != expectedArea
44            || cornerCount.getOrDefault(new Point(minX, minY), 0) != 1
45            || cornerCount.getOrDefault(new Point(minX, maxY), 0) != 1
46            || cornerCount.getOrDefault(new Point(maxX, maxY), 0) != 1
47            || cornerCount.getOrDefault(new Point(maxX, minY), 0) != 1) {
48            return false;
49        }
50
51        // Remove the four corners of the overall rectangle
52        cornerCount.remove(new Point(minX, minY));
53        cornerCount.remove(new Point(minX, maxY));
54        cornerCount.remove(new Point(maxX, maxY));
55        cornerCount.remove(new Point(maxX, minY));
56
57        // All remaining internal corners should appear exactly 2 or 4 times
58        // 2 times: corner shared by 2 rectangles along an edge
59        // 4 times: corner shared by 4 rectangles at an internal point
60        return cornerCount.values().stream().allMatch(count -> count == 2 || count == 4);
61    }
62
63    /**
64     * Helper class to represent a 2D point
65     */
66    private static class Point {
67        final int x;
68        final int y;
69
70        Point(int x, int y) {
71            this.x = x;
72            this.y = y;
73        }
74
75        @Override
76        public boolean equals(Object obj) {
77            if (this == obj) {
78                return true;
79            }
80            if (obj == null || getClass() != obj.getClass()) {
81                return false;
82            }
83            Point point = (Point) obj;
84            return x == point.x && y == point.y;
85        }
86
87        @Override
88        public int hashCode() {
89            return Objects.hash(x, y);
90        }
91    }
92}
93
1#include <bits/stdc++.h>
2using namespace std;
3
4class Solution {
5public:
6    bool isRectangleCover(vector<vector<int>>& rectangles) {
7        // Calculate total area and find the bounding rectangle
8        long long totalArea = 0;
9        int minX = rectangles[0][0], minY = rectangles[0][1];
10        int maxX = rectangles[0][2], maxY = rectangles[0][3];
11      
12        // Map to count occurrences of each corner point
13        // Key: (x, y) coordinate, Value: count of occurrences
14        using Point = pair<int, int>;
15        map<Point, int> cornerCount;
16      
17        // Process each rectangle
18        for (const auto& rect : rectangles) {
19            // Add area of current rectangle
20            totalArea += static_cast<long long>(rect[2] - rect[0]) * (rect[3] - rect[1]);
21          
22            // Update bounding rectangle coordinates
23            minX = min(minX, rect[0]);
24            minY = min(minY, rect[1]);
25            maxX = max(maxX, rect[2]);
26            maxY = max(maxY, rect[3]);
27          
28            // Count occurrences of each corner of the current rectangle
29            // Bottom-left corner
30            ++cornerCount[{rect[0], rect[1]}];
31            // Top-left corner
32            ++cornerCount[{rect[0], rect[3]}];
33            // Top-right corner
34            ++cornerCount[{rect[2], rect[3]}];
35            // Bottom-right corner
36            ++cornerCount[{rect[2], rect[1]}];
37        }
38      
39        // Calculate expected area of the bounding rectangle
40        long long expectedArea = static_cast<long long>(maxX - minX) * (maxY - minY);
41      
42        // Check if total area matches and the four corners of bounding rectangle appear exactly once
43        if (totalArea != expectedArea || 
44            cornerCount[{minX, minY}] != 1 || 
45            cornerCount[{minX, maxY}] != 1 || 
46            cornerCount[{maxX, maxY}] != 1 || 
47            cornerCount[{maxX, minY}] != 1) {
48            return false;
49        }
50      
51        // Remove the four corners of the bounding rectangle
52        cornerCount.erase({minX, minY});
53        cornerCount.erase({minX, maxY});
54        cornerCount.erase({maxX, maxY});
55        cornerCount.erase({maxX, minY});
56      
57        // Check if all remaining corner points appear exactly 2 or 4 times
58        // 2 times: shared edge between two rectangles
59        // 4 times: intersection point of four rectangles
60        return all_of(cornerCount.begin(), cornerCount.end(), 
61                     [](const pair<Point, int>& entry) {
62                         return entry.second == 2 || entry.second == 4;
63                     });
64    }
65};
66
1function isRectangleCover(rectangles: number[][]): boolean {
2    // Calculate total area and find the bounding rectangle
3    let totalArea = 0;
4    let minX = rectangles[0][0];
5    let minY = rectangles[0][1];
6    let maxX = rectangles[0][2];
7    let maxY = rectangles[0][3];
8  
9    // Map to count occurrences of each corner point
10    // Key: "x,y" coordinate string, Value: count of occurrences
11    const cornerCount = new Map<string, number>();
12  
13    // Helper function to create a point key
14    const makePointKey = (x: number, y: number): string => {
15        return `${x},${y}`;
16    };
17  
18    // Process each rectangle
19    for (const rect of rectangles) {
20        // Add area of current rectangle
21        totalArea += (rect[2] - rect[0]) * (rect[3] - rect[1]);
22      
23        // Update bounding rectangle coordinates
24        minX = Math.min(minX, rect[0]);
25        minY = Math.min(minY, rect[1]);
26        maxX = Math.max(maxX, rect[2]);
27        maxY = Math.max(maxY, rect[3]);
28      
29        // Count occurrences of each corner of the current rectangle
30        // Bottom-left corner
31        const bottomLeft = makePointKey(rect[0], rect[1]);
32        cornerCount.set(bottomLeft, (cornerCount.get(bottomLeft) || 0) + 1);
33      
34        // Top-left corner
35        const topLeft = makePointKey(rect[0], rect[3]);
36        cornerCount.set(topLeft, (cornerCount.get(topLeft) || 0) + 1);
37      
38        // Top-right corner
39        const topRight = makePointKey(rect[2], rect[3]);
40        cornerCount.set(topRight, (cornerCount.get(topRight) || 0) + 1);
41      
42        // Bottom-right corner
43        const bottomRight = makePointKey(rect[2], rect[1]);
44        cornerCount.set(bottomRight, (cornerCount.get(bottomRight) || 0) + 1);
45    }
46  
47    // Calculate expected area of the bounding rectangle
48    const expectedArea = (maxX - minX) * (maxY - minY);
49  
50    // Check if total area matches and the four corners of bounding rectangle appear exactly once
51    const boundingCornerBL = makePointKey(minX, minY);
52    const boundingCornerTL = makePointKey(minX, maxY);
53    const boundingCornerTR = makePointKey(maxX, maxY);
54    const boundingCornerBR = makePointKey(maxX, minY);
55  
56    if (totalArea !== expectedArea || 
57        cornerCount.get(boundingCornerBL) !== 1 || 
58        cornerCount.get(boundingCornerTL) !== 1 || 
59        cornerCount.get(boundingCornerTR) !== 1 || 
60        cornerCount.get(boundingCornerBR) !== 1) {
61        return false;
62    }
63  
64    // Remove the four corners of the bounding rectangle
65    cornerCount.delete(boundingCornerBL);
66    cornerCount.delete(boundingCornerTL);
67    cornerCount.delete(boundingCornerTR);
68    cornerCount.delete(boundingCornerBR);
69  
70    // Check if all remaining corner points appear exactly 2 or 4 times
71    // 2 times: shared edge between two rectangles
72    // 4 times: intersection point of four rectangles
73    for (const [point, count] of cornerCount) {
74        if (count !== 2 && count !== 4) {
75            return false;
76        }
77    }
78  
79    return true;
80}
81

Time and Space Complexity

Time Complexity: O(n) where n is the number of rectangles in the input list.

  • The algorithm iterates through all rectangles once in the main loop: O(n)
  • For each rectangle, it performs constant time operations:
    • Area calculation: O(1)
    • Min/max updates: O(1)
    • Adding 4 corner points to the hashmap: O(1) for each insertion
  • After the loop, checking the 4 outer corners: O(1)
  • Deleting 4 corners from the hashmap: O(1)
  • The final validation iterates through all remaining points in the hashmap. In the worst case, there are 4n corner points total, minus the 4 outer corners, giving us O(n) points to check
  • Overall: O(n) + O(n) = O(n)

Space Complexity: O(n)

  • The cnt dictionary stores corner points. Each rectangle contributes 4 corners, but corners can be shared between adjacent rectangles
  • In the worst case (no overlapping corners except at boundaries), we have approximately 4n unique corner points
  • Therefore, the space complexity is O(n) for storing the corner point frequencies in the hashmap

Common Pitfalls

1. Integer Overflow in Area Calculation

When calculating areas with large coordinate values, the multiplication (x2 - x1) * (y2 - y1) can cause integer overflow in languages with fixed integer sizes. While Python handles arbitrary precision integers automatically, this is a critical issue in languages like Java or C++.

Solution: Use appropriate data types (e.g., long in Java) or check for potential overflow before multiplication:

# For other languages, ensure using appropriate data type
# Java: use long for area calculations
# C++: use long long or check for overflow

2. Incorrect Corner Identification for Overlapping Rectangles

A subtle pitfall occurs when trying to detect overlaps through corner counting alone. If two rectangles overlap perfectly (one completely inside another), the corner counting method might not detect this as an invalid case if the counts happen to work out.

Example of edge case:

# These rectangles overlap but might pass basic corner counting
rectangles = [[0,0,2,2], [0,0,1,1], [1,1,2,2]]  # Small square inside larger square

Solution: The algorithm actually handles this through the area check - overlapping rectangles would cause the total area to be less than expected, failing the area condition.

3. Floating Point Precision Issues

If the problem involved floating-point coordinates instead of integers, comparing areas with == would be problematic due to floating-point precision errors.

Solution: For floating-point comparisons, use an epsilon value:

EPSILON = 1e-9
if abs(total_area - (max_x - min_x) * (max_y - min_y)) > EPSILON:
    return False

4. Not Handling Edge Case of Single Rectangle

The code might seem to have issues with a single rectangle input, but it actually handles it correctly. A single rectangle has four corners that each appear once, which satisfies both the outer corner condition and leaves no internal corners to check.

Solution: The current implementation handles this correctly, but it's worth adding a comment or explicit check for clarity:

# Single rectangle is always a valid exact cover of itself
if len(rectangles) == 1:
    return True

5. Memory Issues with Large Coordinate Spaces

If coordinates can be very large but sparse, storing them as tuples in a dictionary is memory-efficient. However, attempting to use a 2D array for counting would cause memory issues.

Solution: The current approach using a dictionary with tuple keys is optimal for sparse coordinate spaces. Avoid array-based solutions unless coordinates are guaranteed to be small and dense.

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

Which algorithm should you use to find a node that is close to the root of the tree?


Recommended Readings

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

Load More