Random Point in Non-overlapping Rectangles
MedPrompt
You are given an array of non-overlapping axis-aligned rectangles rects where rects[i] = [ai, bi, xi, yi] indicates that (ai, bi) is the bottom-left corner point of the ith rectangle and (xi, yi) is the top-right corner point of the ith rectangle. Design an algorithm to pick a random integer point inside the space covered by one of the given rectangles. A point on the perimeter of a rectangle is included in the space covered by the rectangle.
Any integer point inside the space covered by one of the given rectangles should be equally likely to be returned.
Note that an integer point is a point that has integer coordinates.
Implement the Solution class:
Solution(int[][] rects)Initializes the object with the given rectanglesrects.int[] pick()Returns a random integer point[u, v]inside the space covered by one of the given rectangles.
Example 1:
Input
["Solution", "pick", "pick", "pick", "pick", "pick"]
[[[[-2, -2, 1, 1], [2, 2, 4, 6]]], [], [], [], [], []]
Output
[null, [1, -2], [1, -1], [-1, -2], [-2, -2], [0, 0]]
Explanation
Solution solution = new Solution([[-2, -2, 1, 1], [2, 2, 4, 6]]);
solution.pick(); // return [1, -2]
solution.pick(); // return [1, -1]
solution.pick(); // return [-1, -2]
solution.pick(); // return [-2, -2]
solution.pick(); // return [0, 0]
Constraints:
1 <= rects.length <= 100rects[i].length == 4-109 <= ai < xi <= 109-109 <= bi < yi <= 109xi - ai <= 2000yi - bi <= 2000- All the rectangles do not overlap.
- At most
104calls will be made topick.
Approaches
2 approaches with complexity analysis and trade-offs.
This approach involves two main steps. First, we pre-calculate the number of integer points each rectangle contains and the total number of points. To pick a point, we first select a rectangle with a probability proportional to its number of points, and then we pick a uniform random point within that chosen rectangle. The rectangle selection is done by linearly scanning through the rectangles' point counts.
Algorithm
- Constructor (
Solution(int[][] rects)):- Initialize an array or list,
pointCounts, to store the number of integer points for each rectangle. - Initialize a variable
totalPointsto 0. - For each rectangle
rectinrects:- Calculate the number of points:
count = (rect[2] - rect[0] + 1) * (rect[3] - rect[1] + 1). - Add
counttopointCounts. - Add
counttototalPoints.
- Calculate the number of points:
- Initialize an array or list,
- Pick Method (
int[] pick()):- Generate a random integer
targetbetween 1 andtotalPoints. - Iterate through the
pointCountsfrom indexi = 0toN-1:- If
targetis less than or equal topointCounts[i], this means the point falls into the current rectanglei. Select this rectangle and break the loop. - Otherwise, subtract
pointCounts[i]fromtargetand continue to the next rectangle.
- If
- Once a rectangle
[a, b, x, y]is chosen:- Generate a random x-coordinate
uin the range[a, x]. - Generate a random y-coordinate
vin the range[b, y].
- Generate a random x-coordinate
- Return the point
[u, v].
- Generate a random integer
Walkthrough
Constructor (Solution(int[][] rects))
In the constructor, we process the input rectangles to prepare for the pick operation. We iterate through each rectangle [a, b, x, y] and calculate the number of integer points it contains using the formula (x - a + 1) * (y - b + 1). These counts are stored in a list, and we also compute the sum of all these counts, which gives us the totalPoints across all rectangles.
Pick Method (int[] pick())
To ensure every point has an equal chance of being selected, we use a weighted random selection strategy.
- We pick a random integer
targetfrom 1 tototalPoints. Thistargetconceptually represents the k-th point if all points from all rectangles were laid out in a single sequence. - We then iterate through our list of point counts. For each rectangle, we check if our
targetfalls within the range of points contributed by that rectangle. We do this by seeing iftargetis less than or equal to the current rectangle's point count. If it is, we've found our rectangle. If not, we subtract the current rectangle's point count fromtargetand move to the next one. - After identifying the correct rectangle, we generate a random integer point within its boundaries. A random x-coordinate is chosen from its horizontal range, and a random y-coordinate from its vertical range. This final point
[u, v]is then returned.
import java.util.ArrayList;import java.util.List;import java.util.Random; class Solution { private int[][] rects; private List<Integer> pointCounts; private int totalPoints; private Random random; public Solution(int[][] rects) { this.rects = rects; this.pointCounts = new ArrayList<>(); this.totalPoints = 0; this.random = new Random(); for (int[] rect : rects) { int pointsInRect = (rect[2] - rect[0] + 1) * (rect[3] - rect[1] + 1); this.totalPoints += pointsInRect; this.pointCounts.add(pointsInRect); } } public int[] pick() { int target = random.nextInt(totalPoints) + 1; // 1-based index int rectIndex = 0; for (int i = 0; i < pointCounts.size(); i++) { if (target <= pointCounts.get(i)) { rectIndex = i; break; } target -= pointCounts.get(i); } int[] rect = rects[rectIndex]; int x1 = rect[0], y1 = rect[1], x2 = rect[2], y2 = rect[3]; int randX = x1 + random.nextInt(x2 - x1 + 1); int randY = y1 + random.nextInt(y2 - y1 + 1); return new int[]{randX, randY}; }}Complexity
Time
- **Constructor:** `O(N)`, where N is the number of rectangles, as we iterate through them once to calculate point counts. - **`pick()`:** `O(N)` in the worst case, as we might need to iterate through all N rectangles to select one.
Space
O(N) to store the point counts for each of the N rectangles.
Trade-offs
Pros
Relatively simple to understand and implement.
Correctly provides a uniform distribution over all possible integer points.
Constructor is efficient with O(N) time complexity.
Cons
The
pick()operation has a time complexity of O(N), where N is the number of rectangles. This can be slow ifpick()is called many times with a large N.
This is an optimized version of the weighted random selection approach. Instead of linearly scanning to find the right rectangle, we use a more efficient data structure. By pre-calculating the prefix sums of the point counts, we can use binary search to select a rectangle in logarithmic time. This significantly improves the performance of the pick operation, especially when it's called many times.
Algorithm
- Constructor (
Solution(int[][] rects)):- Initialize a list,
prefixSums, to store the cumulative sum of points. - Initialize a running sum
currentSum = 0. - For each rectangle
rectinrects:- Calculate
count = (rect[2] - rect[0] + 1) * (rect[3] - rect[1] + 1). - Add
counttocurrentSum. - Add the new
currentSumto theprefixSumslist.
- Calculate
- The last element of
prefixSumsis the total number of points.
- Initialize a list,
- Pick Method (
int[] pick()):- Get the total number of points,
totalPoints, from the last element ofprefixSums. - Generate a random integer
targetbetween 0 andtotalPoints - 1. - Use binary search on
prefixSumsto find the first indexiwheretarget < prefixSums[i]. This indexiis the index of the chosen rectangle. - Once a rectangle
[a, b, x, y]is chosen:- Generate a random x-coordinate
uin the range[a, x]. - Generate a random y-coordinate
vin the range[b, y].
- Generate a random x-coordinate
- Return the point
[u, v].
- Get the total number of points,
Walkthrough
Constructor (Solution(int[][] rects))
The key idea is to map the 2D problem into a 1D problem. We can imagine all the integer points from all rectangles concatenated into a single, long array. The constructor's job is to figure out the size of this conceptual array and the boundaries for each rectangle's segment within it. We create a prefixSums array where prefixSums[i] stores the total number of points in rectangles 0 through i. This is done by iterating through the rectangles, calculating the points in each, and keeping a running total which is stored at each step.
Pick Method (int[] pick())
With the prefixSums array, picking a point becomes a two-step process:
- Select a Rectangle: We generate a random integer
targetfrom0tototalPoints - 1. Thistargetis an index into our conceptual 1D array of all points. We then use binary search on theprefixSumsarray to efficiently find which rectangle this index falls into. The binary search looks for the first cumulative sum that is greater than ourtargetvalue. The index of this sum corresponds to the index of our chosen rectangle. - Select a Point in the Rectangle: Once the rectangle is chosen, we generate a random point within its boundaries, just as in the previous approach. A random x-coordinate is chosen from
[x1, x2]and a random y-coordinate from[y1, y2].
This method reduces the time complexity of picking a rectangle from O(N) to O(log N).
import java.util.ArrayList;import java.util.List;import java.util.Random; class Solution { private int[][] rects; private List<Integer> prefixSums; private int totalPoints; private Random random; public Solution(int[][] rects) { this.rects = rects; this.prefixSums = new ArrayList<>(); this.random = new Random(); int currentSum = 0; for (int[] rect : rects) { int pointsInRect = (rect[2] - rect[0] + 1) * (rect[3] - rect[1] + 1); currentSum += pointsInRect; prefixSums.add(currentSum); } this.totalPoints = currentSum; } public int[] pick() { int target = random.nextInt(totalPoints); // Binary search to find the rectangle index int low = 0, high = prefixSums.size() - 1; int rectIndex = 0; while (low <= high) { int mid = low + (high - low) / 2; if (target < prefixSums.get(mid)) { rectIndex = mid; high = mid - 1; } else { low = mid + 1; } } int[] rect = rects[rectIndex]; int x1 = rect[0], y1 = rect[1], x2 = rect[2], y2 = rect[3]; int randX = x1 + random.nextInt(x2 - x1 + 1); int randY = y1 + random.nextInt(y2 - y1 + 1); return new int[]{randX, randY}; }}Complexity
Time
- **Constructor:** `O(N)`, where N is the number of rectangles, to build the prefix sum array. - **`pick()`:** `O(log N)` due to the binary search on the prefix sum array.
Space
O(N) to store the prefix sum array for the N rectangles.
Trade-offs
Pros
Highly efficient
pick()operation with O(log N) time complexity.Maintains the correctness of uniform random point selection.
Optimal for scenarios where
pick()is called frequently.
Cons
Slightly more complex to implement due to the binary search logic.
Solutions
Solution
class Solution {private int[] s;private int[][] rects;private Random random = new Random();public Solution(int[][] rects) { int n = rects.length; s = new int[n + 1]; for (int i = 0; i < n; ++i) { s[i + 1] = s[i] + (rects[i][2] - rects[i][0] + 1) * (rects[i][3] - rects[i][1] + 1); } this.rects = rects; }public int[] pick() { int n = rects.length; int v = 1 + random.nextInt(s[n]); int left = 0, right = n; while (left < right) { int mid = (left + right) >> 1; if (s[mid] >= v) { right = mid; } else { left = mid + 1; } } int[] rect = rects[left - 1]; return new int[]{rect[0] + random.nextInt(rect[2] - rect[0] + 1), rect[1] + random.nextInt(rect[3] - rect[1] + 1)}; }} /** * Your Solution object will be instantiated and called as such: * Solution obj = new Solution(rects); * int[] param_1 = obj.pick(); */class Solution { public: vector < int > s ; vector < vector < int >> rects ; Solution ( vector < vector < int >>& rects ) { int n = rects . size (); s . resize ( n + 1 ); for ( int i = 0 ; i < n ; ++ i ) s [ i + 1 ] = s [ i ] + ( rects [ i ][ 2 ] - rects [ i ][ 0 ] + 1 ) * ( rects [ i ][ 3 ] - rects [ i ][ 1 ] + 1 ); this -> rects = rects ; srand ( time ( nullptr )); } vector < int > pick () { int n = rects . size (); int v = 1 + rand () % s [ n ]; int idx = lower_bound ( s . begin (), s . end (), v ) - s . begin (); auto & rect = rects [ idx - 1 ]; int x = rect [ 0 ] + rand () % ( rect [ 2 ] - rect [ 0 ] + 1 ); int y = rect [ 1 ] + rand () % ( rect [ 3 ] - rect [ 1 ] + 1 ); return { x , y }; } }; /** * Your Solution object will be instantiated and called as such: * Solution* obj = new Solution(rects); * vector<int> param_1 = obj->pick(); */class Solution: # Your Solution object will be instantiated and called as such: # obj = Solution(rects) # param_1 = obj.pick() def __init__(self, rects: List[List[int]]): self . rects = rects self . s = [0] * len(rects) for i, (x1, y1, x2, y2) in enumerate(rects): self . s[i] = self . s[i - 1] + (x2 - x1 + 1) * (y2 - y1 + 1) def pick(self) -> List[int]: v = random . randint(1, self . s[- 1]) idx = bisect_left(self . s, v) x1, y1, x2, y2 = self . rects[idx] return [random . randint(x1, x2), random . randint(y1, y2)]Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.