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+1is crucial because the boundaries are inclusive. - 
Build a cumulative sum array where
s[i]represents the total number of integer points in rectangles0throughi. This creates ranges where rectanglei"owns" the integers froms[i-1] + 1tos[i]. - 
To pick a point, we generate a random number between
1and 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.swith 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
vbetween 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_leftreturns the leftmost position wherevcould 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_leftreturns 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 5-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 = 2points - Height: 
3 - 1 + 1 = 3points - Total points: 
2 × 3 = 6points - 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 = 3points - Height: 
3 - 2 + 1 = 2points - Total points: 
3 × 2 = 6points - 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 = 2points - Height: 
5 - 5 + 1 = 1point - Total points: 
2 × 1 = 2points - 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_leftreturns 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()
551class 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 */
751class 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 */
481let 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 */
55Time and Space Complexity
Time Complexity:
__init__:O(n)wherenis 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_leftto find the target rectangle, plusO(1)for generating random coordinates within the selected rectangle.
Space Complexity:
O(n)wherenis 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_indexis between two prefix sum values,bisect_leftreturns 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) = 4points - Correct: 
(3-1+1) * (3-1+1) = 9points 
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.
In a binary min heap, the maximum element can be found in:
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!