497. Random Point in Non-overlapping Rectangles
Problem Description
You are given an array of non-overlapping axis-aligned rectangles where each rectangle is defined by four coordinates: [a_i, b_i, x_i, y_i]
. The point (a_i, b_i)
represents the bottom-left corner and (x_i, y_i)
represents the top-right corner of the i-th rectangle.
Your task is to design a data structure that can randomly pick an integer point from the space covered by all the rectangles. The key requirements are:
-
Integer Points Only: You must return points with integer coordinates (e.g.,
[3, 5]
not[3.5, 5.2]
) -
Inclusive Boundaries: Points on the perimeter of rectangles are considered inside the rectangle
-
Uniform Distribution: Every integer point within any rectangle should have an equal probability of being selected
You need to implement a Solution
class with two methods:
__init__(rects)
: Initialize the data structure with the given rectanglespick()
: Return a random integer point[u, v]
that lies inside one of the rectangles
Example: If you have a rectangle [1, 1, 3, 3]
, the valid integer points would be:
(1,1), (1,2), (1,3)
(2,1), (2,2), (2,3)
(3,1), (3,2), (3,3)
That's 9 total points (a 3×3 grid), and each should have a 1/9 probability of being selected when this is the only rectangle.
The challenge is handling multiple rectangles of different sizes while maintaining uniform probability across all valid points in all rectangles.
Intuition
The key insight is that we need to ensure every integer point across all rectangles has an equal probability of being selected. If we simply picked a random rectangle first and then a random point within it, larger rectangles would have their points selected less frequently than smaller rectangles.
Consider two rectangles: one with 100 integer points and another with just 10 integer points. If we pick each rectangle with 50% probability, then each point in the small rectangle has a 5% chance of being selected (50% × 1/10), while each point in the large rectangle only has a 0.5% chance (50% × 1/100). This violates the uniform distribution requirement.
The solution is to use weighted random selection based on the number of integer points in each rectangle. We need to:
-
Calculate how many integer points each rectangle contains. For a rectangle
[x1, y1, x2, y2]
, the number of integer points is(x2 - x1 + 1) × (y2 - y1 + 1)
. The+1
is crucial because the boundaries are inclusive. -
Build a cumulative sum array where
s[i]
represents the total number of integer points in rectangles0
throughi
. This creates ranges where rectanglei
"owns" the integers froms[i-1] + 1
tos[i]
. -
To pick a point, we generate a random number between
1
and the total number of points across all rectangles. We then use binary search to find which rectangle this number falls into based on our cumulative sum array. -
Once we know which rectangle to use, we can uniformly pick a random integer point within that specific rectangle using
random.randint(x1, x2)
andrandom.randint(y1, y2)
.
This approach ensures that if there are 110 total integer points (100 in the first rectangle and 10 in the second), each individual point has exactly a 1/110 probability of being selected, regardless of which rectangle it belongs to.
Solution Approach
The implementation uses a prefix sum array combined with binary search to efficiently select rectangles based on their point counts.
Initialization (__init__
method):
-
Store the rectangles array for later use:
self.rects = rects
-
Create a prefix sum array
self.s
with the same length as the number of rectangles. Each elements[i]
will store the cumulative count of all integer points from rectangle 0 to rectangle i. -
For each rectangle
[x1, y1, x2, y2]
:- Calculate the number of integer points:
(x2 - x1 + 1) * (y2 - y1 + 1)
- Add this count to the previous cumulative sum:
self.s[i] = self.s[i - 1] + point_count
- Note: When
i = 0
,self.s[i - 1]
gives usself.s[-1]
which Python interprets as the last element, but since we initialized with zeros, this works correctly
- Calculate the number of integer points:
Pick Method (pick
):
-
Generate a random integer
v
between 1 and the total number of points (stored inself.s[-1]
). This represents selecting the v-th point among all points. -
Use
bisect_left(self.s, v)
to find which rectangle contains the v-th point:bisect_left
returns the leftmost position wherev
could be inserted to keep the array sorted- This gives us the index of the rectangle that "owns" this point number
- For example, if
s = [0, 9, 19, 25]
andv = 15
,bisect_left
returns index 2, meaning the point is in rectangle 2
-
Retrieve the selected rectangle's coordinates:
x1, y1, x2, y2 = self.rects[idx]
-
Uniformly select a random integer point within this rectangle:
- Random x-coordinate:
random.randint(x1, x2)
- Random y-coordinate:
random.randint(y1, y2)
- Return
[x, y]
- Random x-coordinate:
Time Complexity:
- Initialization:
O(n)
where n is the number of rectangles - Pick:
O(log n)
for binary search plusO(1)
for random point generation
Space Complexity:
O(n)
to store the rectangles and prefix sum array
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 concrete example with three rectangles to illustrate how the solution works:
Given rectangles:
- Rectangle 0:
[1, 1, 2, 3]
- Rectangle 1:
[3, 2, 5, 3]
- Rectangle 2:
[2, 5, 3, 5]
Step 1: Calculate integer points in each rectangle
For Rectangle 0 [1, 1, 2, 3]
:
- Width:
2 - 1 + 1 = 2
points - Height:
3 - 1 + 1 = 3
points - Total points:
2 × 3 = 6
points - These are:
(1,1), (1,2), (1,3), (2,1), (2,2), (2,3)
For Rectangle 1 [3, 2, 5, 3]
:
- Width:
5 - 3 + 1 = 3
points - Height:
3 - 2 + 1 = 2
points - Total points:
3 × 2 = 6
points - These are:
(3,2), (3,3), (4,2), (4,3), (5,2), (5,3)
For Rectangle 2 [2, 5, 3, 5]
:
- Width:
3 - 2 + 1 = 2
points - Height:
5 - 5 + 1 = 1
point - Total points:
2 × 1 = 2
points - These are:
(2,5), (3,5)
Step 2: Build prefix sum array
Starting with s = [0, 0, 0]
:
s[0] = 0 + 6 = 6
(Rectangle 0 has 6 points)s[1] = 6 + 6 = 12
(Rectangle 0 + Rectangle 1 = 12 points)s[2] = 12 + 2 = 14
(All rectangles = 14 points total)
Final prefix sum array: s = [6, 12, 14]
This means:
- Points 1-6 belong to Rectangle 0
- Points 7-12 belong to Rectangle 1
- Points 13-14 belong to Rectangle 2
Step 3: Simulate picking a random point
Let's say random.randint(1, 14)
returns 10
.
Using bisect_left(s, 10)
:
- We search for where 10 would fit in
[6, 12, 14]
- Since 10 > 6 but 10 ≤ 12,
bisect_left
returns index 1 - This means we select Rectangle 1:
[3, 2, 5, 3]
Now we pick a random point within Rectangle 1:
- Random x:
random.randint(3, 5)
→ let's say it returns4
- Random y:
random.randint(2, 3)
→ let's say it returns3
- Return
[4, 3]
Probability verification: Each of the 14 integer points has exactly 1/14 probability of being selected:
- Rectangle 0's 6 points: Each has probability
(6/14) × (1/6) = 1/14
✓ - Rectangle 1's 6 points: Each has probability
(6/14) × (1/6) = 1/14
✓ - Rectangle 2's 2 points: Each has probability
(2/14) × (1/2) = 1/14
✓
The weighted selection ensures uniform distribution across all points regardless of which rectangle they belong to.
Solution Implementation
1from typing import List
2import random
3from bisect import bisect_left
4
5
6class Solution:
7 def __init__(self, rects: List[List[int]]):
8 """
9 Initialize with a list of non-overlapping rectangles.
10 Each rectangle is represented as [x1, y1, x2, y2] where
11 (x1, y1) is bottom-left corner and (x2, y2) is top-right corner.
12 """
13 self.rects = rects
14 # Create prefix sum array to store cumulative count of points
15 self.prefix_sums = [0] * len(rects)
16
17 for i, (x1, y1, x2, y2) in enumerate(rects):
18 # Calculate number of integer points in current rectangle
19 points_count = (x2 - x1 + 1) * (y2 - y1 + 1)
20
21 # Build prefix sum: add current rectangle's points to previous sum
22 if i == 0:
23 self.prefix_sums[i] = points_count
24 else:
25 self.prefix_sums[i] = self.prefix_sums[i - 1] + points_count
26
27 def pick(self) -> List[int]:
28 """
29 Pick a random integer point from one of the rectangles.
30 Returns [x, y] coordinates of the selected point.
31 """
32 # Pick a random point index from 1 to total number of points
33 random_point_index = random.randint(1, self.prefix_sums[-1])
34
35 # Find which rectangle contains this point using binary search
36 rectangle_index = bisect_left(self.prefix_sums, random_point_index)
37
38 # Handle edge case where bisect_left returns exact match
39 if rectangle_index < len(self.prefix_sums) and self.prefix_sums[rectangle_index] < random_point_index:
40 rectangle_index += 1
41
42 # Get the selected rectangle's coordinates
43 x1, y1, x2, y2 = self.rects[rectangle_index]
44
45 # Pick a random point within the selected rectangle
46 random_x = random.randint(x1, x2)
47 random_y = random.randint(y1, y2)
48
49 return [random_x, random_y]
50
51
52# Your Solution object will be instantiated and called as such:
53# obj = Solution(rects)
54# param_1 = obj.pick()
55
1class Solution {
2 // Prefix sum array to store cumulative point counts
3 private int[] prefixSum;
4 // Store the input rectangles
5 private int[][] rectangles;
6 // Random number generator
7 private Random random = new Random();
8
9 /**
10 * Constructor initializes the data structure with given rectangles.
11 * Calculates prefix sum of total points in each rectangle.
12 *
13 * @param rects Array of rectangles, each defined as [x1, y1, x2, y2]
14 */
15 public Solution(int[][] rects) {
16 int numRectangles = rects.length;
17 prefixSum = new int[numRectangles + 1];
18
19 // Calculate prefix sum of points in each rectangle
20 for (int i = 0; i < numRectangles; i++) {
21 // Calculate number of integer points in rectangle i
22 int width = rects[i][2] - rects[i][0] + 1;
23 int height = rects[i][3] - rects[i][1] + 1;
24 int pointsInRectangle = width * height;
25
26 // Add to prefix sum
27 prefixSum[i + 1] = prefixSum[i] + pointsInRectangle;
28 }
29
30 this.rectangles = rects;
31 }
32
33 /**
34 * Picks a random integer point from one of the rectangles.
35 * Each point has equal probability of being selected.
36 *
37 * @return Array containing [x, y] coordinates of the selected point
38 */
39 public int[] pick() {
40 int numRectangles = rectangles.length;
41
42 // Generate random number from 1 to total number of points
43 int randomPointIndex = 1 + random.nextInt(prefixSum[numRectangles]);
44
45 // Binary search to find which rectangle contains this point
46 int left = 0;
47 int right = numRectangles;
48
49 while (left < right) {
50 int mid = (left + right) >> 1; // Equivalent to (left + right) / 2
51
52 if (prefixSum[mid] >= randomPointIndex) {
53 right = mid;
54 } else {
55 left = mid + 1;
56 }
57 }
58
59 // Get the selected rectangle (left - 1 because of 1-based indexing in prefix sum)
60 int[] selectedRectangle = rectangles[left - 1];
61
62 // Generate random point within the selected rectangle
63 int randomX = selectedRectangle[0] + random.nextInt(selectedRectangle[2] - selectedRectangle[0] + 1);
64 int randomY = selectedRectangle[1] + random.nextInt(selectedRectangle[3] - selectedRectangle[1] + 1);
65
66 return new int[] {randomX, randomY};
67 }
68}
69
70/**
71 * Your Solution object will be instantiated and called as such:
72 * Solution obj = new Solution(rects);
73 * int[] param_1 = obj.pick();
74 */
75
1class Solution {
2public:
3 vector<int> prefixSum; // Cumulative sum of areas for weighted random selection
4 vector<vector<int>> rectangles; // Store the input rectangles
5
6 Solution(vector<vector<int>>& rects) {
7 int numRects = rects.size();
8 prefixSum.resize(numRects + 1);
9
10 // Build prefix sum array where each element represents
11 // the cumulative area up to that rectangle
12 for (int i = 0; i < numRects; ++i) {
13 int width = rects[i][2] - rects[i][0] + 1; // x2 - x1 + 1 (inclusive)
14 int height = rects[i][3] - rects[i][1] + 1; // y2 - y1 + 1 (inclusive)
15 int area = width * height;
16 prefixSum[i + 1] = prefixSum[i] + area;
17 }
18
19 this->rectangles = rects;
20 srand(time(nullptr)); // Initialize random seed
21 }
22
23 vector<int> pick() {
24 int numRects = rectangles.size();
25
26 // Generate random value in range [1, totalArea]
27 int randomValue = 1 + rand() % prefixSum[numRects];
28
29 // Binary search to find which rectangle this random value falls into
30 int rectIndex = lower_bound(prefixSum.begin(), prefixSum.end(), randomValue) - prefixSum.begin();
31
32 // Get the selected rectangle (adjust index by -1 since prefixSum is 1-indexed)
33 auto& selectedRect = rectangles[rectIndex - 1];
34
35 // Generate random point within the selected rectangle
36 int randomX = selectedRect[0] + rand() % (selectedRect[2] - selectedRect[0] + 1);
37 int randomY = selectedRect[1] + rand() % (selectedRect[3] - selectedRect[1] + 1);
38
39 return {randomX, randomY};
40 }
41};
42
43/**
44 * Your Solution object will be instantiated and called as such:
45 * Solution* obj = new Solution(rects);
46 * vector<int> param_1 = obj->pick();
47 */
48
1let prefixSum: number[] = []; // Cumulative sum of areas for weighted random selection
2let rectangles: number[][] = []; // Store the input rectangles
3
4function Solution(rects: number[][]): void {
5 const numRects = rects.length;
6 prefixSum = new Array(numRects + 1).fill(0);
7
8 // Build prefix sum array where each element represents
9 // the cumulative area up to that rectangle
10 for (let i = 0; i < numRects; i++) {
11 const width = rects[i][2] - rects[i][0] + 1; // x2 - x1 + 1 (inclusive)
12 const height = rects[i][3] - rects[i][1] + 1; // y2 - y1 + 1 (inclusive)
13 const area = width * height;
14 prefixSum[i + 1] = prefixSum[i] + area;
15 }
16
17 rectangles = rects;
18}
19
20function pick(): number[] {
21 const numRects = rectangles.length;
22
23 // Generate random value in range [1, totalArea]
24 const totalArea = prefixSum[numRects];
25 const randomValue = Math.floor(Math.random() * totalArea) + 1;
26
27 // Binary search to find which rectangle this random value falls into
28 let left = 0;
29 let right = prefixSum.length - 1;
30 while (left < right) {
31 const mid = Math.floor((left + right) / 2);
32 if (prefixSum[mid] < randomValue) {
33 left = mid + 1;
34 } else {
35 right = mid;
36 }
37 }
38 const rectIndex = left;
39
40 // Get the selected rectangle (adjust index by -1 since prefixSum is 1-indexed)
41 const selectedRect = rectangles[rectIndex - 1];
42
43 // Generate random point within the selected rectangle
44 const randomX = selectedRect[0] + Math.floor(Math.random() * (selectedRect[2] - selectedRect[0] + 1));
45 const randomY = selectedRect[1] + Math.floor(Math.random() * (selectedRect[3] - selectedRect[1] + 1));
46
47 return [randomX, randomY];
48}
49
50/**
51 * Your Solution object will be instantiated and called as such:
52 * Solution(rects);
53 * const param_1 = pick();
54 */
55
Time and Space Complexity
Time Complexity:
__init__
:O(n)
wheren
is the number of rectangles. The method iterates through all rectangles once to calculate the cumulative area sum.pick
:O(log n)
for the binary search usingbisect_left
to find the target rectangle, plusO(1)
for generating random coordinates within the selected rectangle.
Space Complexity:
O(n)
wheren
is the number of rectangles. The solution stores:self.rects
:O(n)
space to store the original rectanglesself.s
:O(n)
space to store the cumulative sum array of areas
The algorithm uses a weighted random selection approach where rectangles with larger areas have proportionally higher chances of being selected. The cumulative sum array enables efficient rectangle selection using binary search.
Common Pitfalls
1. Incorrect Binary Search Logic
The most common pitfall in this solution is misunderstanding how bisect_left
works with the prefix sum array and random point index.
The Problem:
The code has a critical bug in the pick
method. After using bisect_left
, there's unnecessary logic trying to "fix" the rectangle index:
if rectangle_index < len(self.prefix_sums) and self.prefix_sums[rectangle_index] < random_point_index:
rectangle_index += 1
This is incorrect because:
- When
random_point_index
is between two prefix sum values,bisect_left
returns the index of the larger value - The additional adjustment can cause index out of bounds errors or select the wrong rectangle
The Fix:
Use bisect_right
(or bisect
) instead, which naturally gives us the correct rectangle index:
def pick(self) -> List[int]:
# Pick a random point index from 1 to total number of points
random_point_index = random.randint(1, self.prefix_sums[-1])
# Find which rectangle contains this point using binary search
rectangle_index = bisect.bisect_right(self.prefix_sums, random_point_index - 1)
# Get the selected rectangle's coordinates
x1, y1, x2, y2 = self.rects[rectangle_index]
# Pick a random point within the selected rectangle
random_x = random.randint(x1, x2)
random_y = random.randint(y1, y2)
return [random_x, random_y]
2. Off-by-One Error in Point Counting
Developers often forget that both boundaries are inclusive when counting integer points.
The Problem:
Incorrectly calculating points as (x2 - x1) * (y2 - y1)
instead of (x2 - x1 + 1) * (y2 - y1 + 1)
.
Example:
For rectangle [1, 1, 3, 3]
:
- Wrong:
(3-1) * (3-1) = 4
points - Correct:
(3-1+1) * (3-1+1) = 9
points
3. Random Range Selection Error
Using 0-based indexing for the random point selection instead of 1-based.
The Problem:
Using random.randint(0, self.prefix_sums[-1] - 1)
can cause issues with the binary search logic since the prefix sum array conceptually represents cumulative counts starting from 1.
The Fix:
Stick with 1-based indexing: random.randint(1, self.prefix_sums[-1])
and adjust the binary search accordingly.
Which of the following array represent a max heap?
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
Binary Search Speedrun For each of the Speedrun questions you will be given a binary search related problem and a corresponding multiple choice question The multiple choice questions are related to the techniques and template s introduced in the binary search section It's recommended that you have gone through at
Prefix Sum The prefix sum is an incredibly powerful and straightforward technique Its primary goal is to allow for constant time range sum queries on an array What is Prefix Sum The prefix sum of an array at index i is the sum of all numbers from index 0 to i By
Want a Structured Path to Master System Design Too? Don’t Miss This!