2250. Count Number of Rectangles Containing Each Point

MediumBinary Indexed TreeArrayHash TableBinary SearchSorting
Leetcode Link

Problem Description

You are given a set of rectangles and a set of points. Each rectangle has its bottom-left corner fixed at the origin (0, 0) and is defined by its length and height. Your task is to determine how many rectangles contain each given point.

Input Details:

  • rectangles[i] = [li, hi]: The i-th rectangle extends from (0, 0) to (li, hi), where li is the length (x-dimension) and hi is the height (y-dimension)
  • points[j] = [xj, yj]: The j-th point is located at coordinates (xj, yj)

Containment Rules: A rectangle contains a point if:

  • The point's x-coordinate is between 0 and the rectangle's length (inclusive): 0 <= xj <= li
  • The point's y-coordinate is between 0 and the rectangle's height (inclusive): 0 <= yj <= hi
  • Points on the edges or corners of rectangles are considered contained

Output: Return an array count where count[j] represents the number of rectangles that contain the j-th point.

Solution Approach: The solution groups rectangles by their height values in a dictionary. For each height, it maintains a sorted list of the corresponding lengths. When checking a point (x, y), it iterates through all heights greater than or equal to y (since rectangles with smaller heights cannot contain the point). For each valid height, it uses binary search to count how many rectangles at that height have a length greater than or equal to x.

The key optimization is using bisect_left to efficiently find the position where x would be inserted in the sorted list of lengths, then calculating how many rectangles have length >= x by subtracting this position from the total count.

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

Intuition

The naive approach would be to check every point against every rectangle, which would take O(n * m) time where n is the number of rectangles and m is the number of points. We need a smarter way to count containment.

The key insight is that for a point (x, y) to be inside a rectangle, the rectangle must satisfy two conditions:

  1. Its height must be at least y (height >= y)
  2. Its length must be at least x (length >= x)

Since all rectangles start at the origin (0, 0), we can think of this problem geometrically: we need rectangles whose top-right corner is "above and to the right" of our point.

This suggests organizing rectangles by one dimension to make searching easier. If we group rectangles by their height, then for any point (x, y), we only need to check rectangles with height >= y. This eliminates many unnecessary comparisons.

For each valid height group, we still need to count how many rectangles have length >= x. If we pre-sort the lengths within each height group, we can use binary search to quickly find this count. The position where x would be inserted tells us how many rectangles have length < x, so the remaining rectangles have length >= x.

The height constraint naturally filters out invalid rectangles (those too short), while binary search on the sorted lengths efficiently counts valid rectangles within each height group. Since rectangle heights are bounded (typically up to 100 in the problem constraints), iterating through valid heights is manageable, and the binary search makes counting within each group logarithmic rather than linear.

This approach transforms the problem from checking every rectangle individually to a more structured search that leverages sorting and binary search for efficiency.

Learn more about Binary Search and Sorting patterns.

Solution Implementation

1from collections import defaultdict 2from bisect import bisect_left 3from typing import List 4 5class Solution: 6 def countRectangles( 7 self, rectangles: List[List[int]], points: List[List[int]] 8 ) -> List[int]: 9 # Group rectangles by their height (y-coordinate) 10 # Key: height, Value: list of widths for rectangles with that height 11 height_to_widths = defaultdict(list) 12 13 # Store all rectangle widths grouped by their heights 14 for width, height in rectangles: 15 height_to_widths[height].append(width) 16 17 # Sort widths for each height to enable binary search 18 for height in height_to_widths.keys(): 19 height_to_widths[height].sort() 20 21 # Result list to store count of rectangles containing each point 22 result = [] 23 24 # For each query point, count rectangles that contain it 25 for point_x, point_y in points: 26 count = 0 27 28 # Check all rectangles with height >= point_y (heights from point_y to 100) 29 # A rectangle contains the point if its height >= point_y and width >= point_x 30 for rectangle_height in range(point_y, 101): 31 widths_at_height = height_to_widths[rectangle_height] 32 33 # Use binary search to find number of rectangles with width >= point_x 34 # bisect_left finds the insertion point for point_x in sorted list 35 # All elements from this index onwards have width >= point_x 36 count += len(widths_at_height) - bisect_left(widths_at_height, point_x) 37 38 result.append(count) 39 40 return result 41
1class Solution { 2 public int[] countRectangles(int[][] rectangles, int[][] points) { 3 int maxHeight = 101; 4 5 List<Integer>[] rectanglesByHeight = new List[maxHeight]; 6 Arrays.setAll(rectanglesByHeight, k -> new ArrayList<>()); 7 8 for (int[] rectangle : rectangles) { 9 rectanglesByHeight[rectangle[1]].add(rectangle[0]); 10 } 11 12 for (List<Integer> widthList : rectanglesByHeight) { 13 Collections.sort(widthList); 14 } 15 16 int numPoints = points.length; 17 int[] result = new int[numPoints]; 18 19 for (int i = 0; i < numPoints; i++) { 20 int pointX = points[i][0]; 21 int pointY = points[i][1]; 22 int rectangleCount = 0; 23 24 for (int height = pointY; height < maxHeight; height++) { 25 List<Integer> widthsAtHeight = rectanglesByHeight[height]; 26 27 // Binary search using first_true_index template 28 // Find first index where width >= pointX 29 int left = 0; 30 int right = widthsAtHeight.size() - 1; 31 int firstTrueIndex = widthsAtHeight.size(); // Default: no width >= pointX 32 33 while (left <= right) { 34 int mid = left + (right - left) / 2; 35 if (widthsAtHeight.get(mid) >= pointX) { 36 firstTrueIndex = mid; 37 right = mid - 1; 38 } else { 39 left = mid + 1; 40 } 41 } 42 43 // All rectangles from firstTrueIndex to end have width >= pointX 44 rectangleCount += widthsAtHeight.size() - firstTrueIndex; 45 } 46 47 result[i] = rectangleCount; 48 } 49 50 return result; 51 } 52} 53
1class Solution { 2public: 3 vector<int> countRectangles(vector<vector<int>>& rectangles, vector<vector<int>>& points) { 4 const int MAX_HEIGHT = 101; 5 vector<vector<int>> heightToWidths(MAX_HEIGHT); 6 7 for (auto& rectangle : rectangles) { 8 heightToWidths[rectangle[1]].push_back(rectangle[0]); 9 } 10 11 for (auto& widths : heightToWidths) { 12 sort(widths.begin(), widths.end()); 13 } 14 15 vector<int> result; 16 17 for (auto& point : points) { 18 int pointX = point[0]; 19 int pointY = point[1]; 20 int rectangleCount = 0; 21 22 for (int height = pointY; height < MAX_HEIGHT; ++height) { 23 auto& widthsAtHeight = heightToWidths[height]; 24 25 // Binary search using first_true_index template 26 int left = 0; 27 int right = widthsAtHeight.size() - 1; 28 int firstTrueIndex = widthsAtHeight.size(); // Default: no width >= pointX 29 30 while (left <= right) { 31 int mid = left + (right - left) / 2; 32 if (widthsAtHeight[mid] >= pointX) { 33 firstTrueIndex = mid; 34 right = mid - 1; 35 } else { 36 left = mid + 1; 37 } 38 } 39 40 rectangleCount += widthsAtHeight.size() - firstTrueIndex; 41 } 42 43 result.push_back(rectangleCount); 44 } 45 46 return result; 47 } 48}; 49
1function countRectangles(rectangles: number[][], points: number[][]): number[] { 2 const MAX_Y_VALUE = 101; 3 const rectanglesByY: number[][] = Array.from({ length: MAX_Y_VALUE }, () => []); 4 5 for (const [xCoord, yCoord] of rectangles) { 6 rectanglesByY[yCoord].push(xCoord); 7 } 8 9 for (const xCoordinates of rectanglesByY) { 10 xCoordinates.sort((a, b) => a - b); 11 } 12 13 const results: number[] = []; 14 15 for (const [pointX, pointY] of points) { 16 let rectangleCount = 0; 17 18 for (let currentY = pointY; currentY < MAX_Y_VALUE; currentY++) { 19 const xCoordinatesAtY = rectanglesByY[currentY]; 20 21 // Binary search using first_true_index template 22 // Find first index where x >= pointX 23 let left = 0; 24 let right = xCoordinatesAtY.length - 1; 25 let firstTrueIndex = xCoordinatesAtY.length; // Default: no x >= pointX 26 27 while (left <= right) { 28 const mid = Math.floor((left + right) / 2); 29 if (xCoordinatesAtY[mid] >= pointX) { 30 firstTrueIndex = mid; 31 right = mid - 1; 32 } else { 33 left = mid + 1; 34 } 35 } 36 37 rectangleCount += xCoordinatesAtY.length - firstTrueIndex; 38 } 39 40 results.push(rectangleCount); 41 } 42 43 return results; 44} 45

Solution Approach

The solution groups rectangles by height and uses binary search to count containing rectangles.

Step 1: Group Rectangles by Height

height_to_widths = defaultdict(list) for width, height in rectangles:  height_to_widths[height].append(width)

Step 2: Sort Width Lists

for height in height_to_widths.keys():  height_to_widths[height].sort()

Step 3: Query Using Binary Search Template

For each point (x, y), we iterate through valid heights (y to 100) and count rectangles with width >= x.

Binary Search Template (first_true_index):

We find the first index where width >= pointX:

feasible(idx) = widths[idx] >= pointX

Pattern: false, false, ..., true, true, true

left, right = 0, len(widths) - 1 first_true_index = len(widths) # Default: no width >= pointX  while left <= right:  mid = (left + right) // 2  if widths[mid] >= pointX: # feasible  first_true_index = mid  right = mid - 1  else:  left = mid + 1  count = len(widths) - first_true_index

The count of rectangles with width >= pointX is len(widths) - first_true_index.

Time Complexity:

  • Preprocessing: O(n log n) for sorting
  • Per point query: O(h × log n) where h = 100 (max height)
  • Total: O(n log n + m × h × log n)

Space Complexity: O(n) for storing grouped rectangles.

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 to illustrate the solution approach.

Given:

  • Rectangles: [[4, 2], [3, 3], [5, 3], [2, 1]]
  • Points: [[3, 2], [1, 3]]

Step 1: Organize Rectangles by Height

We group rectangles by their height values:

  • Height 1: Rectangle [2, 1] → length = 2
  • Height 2: Rectangle [4, 2] → length = 4
  • Height 3: Rectangles [3, 3] and [5, 3] → lengths = 3, 5

Our dictionary d becomes:

d = {  1: [2],  2: [4],  3: [3, 5] }

Step 2: Sort Length Lists

After sorting (already sorted in this case):

d = {  1: [2],  2: [4],  3: [3, 5] # sorted: [3, 5] }

Step 3: Process Each Point

For Point 1: (3, 2)

  • We need rectangles with height ≥ 2 and length ≥ 3
  • Check height 2: lengths = [4]
    • bisect_left([4], 3) = 0 (3 would be inserted at position 0)
    • Count: 1 - 0 = 1 rectangle (the one with length 4)
  • Check height 3: lengths = [3, 5]
    • bisect_left([3, 5], 3) = 0 (3 already exists at position 0)
    • Count: 2 - 0 = 2 rectangles (both length 3 and 5)
  • Total for point (3, 2): 1 + 2 = 3 rectangles

For Point 2: (1, 3)

  • We need rectangles with height ≥ 3 and length ≥ 1
  • Check height 3: lengths = [3, 5]
    • bisect_left([3, 5], 1) = 0 (1 would be inserted at position 0)
    • Count: 2 - 0 = 2 rectangles (both satisfy length ≥ 1)
  • Total for point (1, 3): 2 rectangles

Visual Representation:

Height  3 | +-----+ +-------+  | | | | |  2 | | Rect3| | Rect4 |  | |[3,3]| | [5,3] |  1 | +-----+ +-------+  | +----+  | |Rect1| +---------+  | |[2,1]| | Rect2 |  0 +--+----+---+-[4,2]---+--------> Length  0 1 2 3 4 5  ^ ^  | |  Point2 Point1  (1,3) (3,2)

Point (3, 2) is contained by rectangles [4, 2], [3, 3], and [5, 3]. Point (1, 3) is contained by rectangles [3, 3] and [5, 3].

Final Output: [3, 2]

Time and Space Complexity

Time Complexity: O(n log n + m * h * log n) where n is the number of rectangles, m is the number of points, and h is the range of possible heights (in this case, 101).

  • Building the dictionary d takes O(n) time to iterate through all rectangles
  • Sorting all the x-coordinates for each unique height takes O(n log n) in the worst case when all rectangles have different heights
  • For each of the m points, we iterate through heights from y to 100 (at most 101 iterations)
  • For each height, we perform a binary search using bisect_left which takes O(log k) where k is the number of rectangles at that height
  • In the worst case, all rectangles could be at the same height, making each binary search O(log n)
  • Total query time: O(m * h * log n)

Space Complexity: O(n)

  • The dictionary d stores all n rectangle x-coordinates grouped by their y-coordinates, using O(n) space
  • The answer list ans uses O(m) space but this is typically considered as output space
  • The auxiliary space used by sorting is O(1) since we sort in-place
  • Overall auxiliary space complexity is O(n)

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Using Wrong Binary Search Template

The problem requires finding the first index where width >= pointX (first true pattern).

Wrong approach:

while left < right:  mid = (left + right) >> 1  if widths[mid] >= pointX:  right = mid  else:  left = mid + 1 return left # Returns left directly

Correct approach (first_true_index template):

first_true_index = len(widths) # Default while left <= right:  mid = (left + right) // 2  if widths[mid] >= pointX:  first_true_index = mid  right = mid - 1  else:  left = mid + 1 return first_true_index

2. Incorrect Default Value for first_true_index

When no width >= pointX exists, first_true_index should default to len(widths) so that count = len(widths) - first_true_index = 0.

Wrong: first_true_index = -1 (would give wrong count) Correct: first_true_index = len(widths)

3. Forgetting to Sort After Grouping

Binary search requires sorted data:

# Missing sort causes incorrect results! for height in height_to_widths.keys():  height_to_widths[height].sort() # Don't forget this!

4. Hardcoding Height Upper Bound

The code assumes heights ≤ 100 (LeetCode constraint). For robustness, calculate the actual max height:

max_height = max(h for _, h in rectangles) if rectangles else 0
Loading...
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

What data structure does Breadth-first search typically uses to store intermediate states?


Recommended Readings

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

Load More