Problem
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.
Examples
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]
Solution
Caution - Randomly Picking Rectangle and Then Point in it Will Not Work
An intuitive solution is to randomly select one rectangle from the rects
and then generate a random point within it. However, this method is flawed. Why?
Consider two rectangles, R1 and R2, where R1 has a larger area than R2.
Let S1
represent the set of points within rectangle R1, and S2
the set of points within rectangle R2. Mathematically, we have:
S1 = {p11, p12, p13, ........................, p1n}
S2 = {p21, p22, p23, ......, p2m}
n > m
It is clear that R1
has a larger area than R2
, so S1
contains more points than S2
(i.e., n > m
).
To randomly pick an integer point within the area covered by one of the given rectangles, simply selecting a rectangle randomly won’t work; the area of each rectangle must be considered.
Method 1 - Pick Rectangle First Using Area and TreeMap and Then Point using Using TreeMap.ceilingKey()
We can do following:
map
: Stores cumulative area as keys and rectangle indices as values.rects
: Stores the input array of rectangles.totalArea
: Stores the sum of the areas of all rectangles.rnd
: ARandom
object for generating random numbers.
Now, we can follow these steps:
- Initialize
rects
: Store the input array of rectangles. - Initialize
map
: Create a newTreeMap
. - Initialize
totalArea
: Set the total area to 0. - Calculate Areas and Populating the Map:
- Iterate over each rectangle to calculate its area.
- Update
totalArea
to include the area of the current rectangle. - Map the cumulative area to the rectangle’s index in the
map
.
- Generate Random Area Index:
- Generate a random number between 0 and
totalArea - 1
, then add 1 to shift the range to [1,totalArea
]. - Use
ceilingKey
to find the smallest key in the map greater than or equal to this random number (c
). This key points to the rectangle that should be picked.
- Generate a random number between 0 and
- Pick a Random Point in the Rectangle:
- Retrieve the rectangle index from the
map
usingc
. - Call
pickInRect
with the selected rectangle to return a random point within it.
- Retrieve the rectangle index from the
Code
Java
class Solution {
private TreeMap<Integer, Integer> map;
private int[][] rects;
private int totalArea;
private Random rnd= new Random();
// rect[0] = left, rect[1] = bottom
// rect[2] = right, rect[3] = top
public Solution(int[][] rects) {
this.rects = rects;
map = new TreeMap<>();
totalArea = 0;
for(int i = 0; i < rects.length; i++) {
int[] rect = rects[i];
// the right part means the number of points can be picked in this rectangle
totalArea += (rect[2] - rect[0] + 1) * (rect[3] - rect[1] + 1);
map.put(totalArea, i);
}
}
public int[] pick() {
// nextInt(sum) returns a num in [0, sum -1]. After added by 1, it becomes [1, sum]
int c = map.ceilingKey(rnd.nextInt(totalArea) + 1);
return pickInRect(rects[map.get(c)]);
}
private int[] pickInRect(int[] rect) {
int left = rect[0], right = rect[2], bottom = rect[1], top = rect[3];
int x = left + rnd.nextInt(right - left + 1);
int y = bottom + rnd.nextInt(top - bottom + 1);
return new int[]{x, y};
}
}
Complexity
Constructor: Solution(int[][] rects)
- ⏰ Time complexity:
O(n log n)
- 🧺 Space complexity:
O(n)
Method: int[] pick()
- ⏰ Time complexity:
O(log n)
- 🧺 Space complexity:
O(1)
Method 2 - Pick Rectangle First Using Area and TreeMap and Then Point using Using TreeMap.floorKey()
We are also saving rectangle in the map, instead of saving rects array. Here are the steps:
Class Initialization:
- Define a
TreeMap<Integer, int[]> areaToRectMap
to map cumulative areas to rectangles. - Initialize
totalArea
to 0. - Initialize a
Random
objectrand
for generating random numbers.
- Define a
Constructor:
- Initialize
areaToRectMap
,totalArea
, andrand
. - Call
buildMap()
to populate theareaToRectMap
.
- Initialize
buildMap Method:
- Iterate through each rectangle in
rects
. - Calculate the area of each rectangle.
- Map the cumulative area to the rectangle in
areaToRectMap
. - Update
totalArea
with the area of the current rectangle.
- Iterate through each rectangle in
pick Method:
- Generate a random integer
currIdx
between 0 andtotalArea - 1
. - Find the largest key in
areaToRectMap
less than or equal tocurrIdx
usingfloorKey
. - Retrieve the rectangle associated with this key.
- Call
getRandomPoint
to generate a random point within the selected rectangle.
- Generate a random integer
getRandomPoint Method:
- Generate a random x-coordinate within the rectangle’s x-range.
- Generate a random y-coordinate within the rectangle’s y-range.
- Return the coordinates as an array.
Code
Java
class Solution {
private final TreeMap<Integer, int[] > areaToRectMap;
private int totalArea;
private final Random rand;
public Solution(int[][] rects) {
this.idx = 0;
this.areaToRectMap = new TreeMap<>();
buildMap();
this.rand = new Random();
}
private void buildMap() {
for (int[] rect: rects) {
int area = (rect[2] - rect[0] + 1) * (rect[3] - rect[1] + 1);
idxRectMap.put(idx, rect);
totalArea += area;
}
}
public int[] pick() {
int currIdx = rand.nextInt(totalArea);
int keyIdx = areaToRectMap.floorKey(currIdx);
int[] rect = areaToRectMap.get(keyIdx);
return getRandomPoint(rect);
}
private int[] getRandomPoint(int[] rect) {
int[] point = new int[2];
point[0] = rand.nextInt(rect[2] - rect[0] + 1) + rect[0];
point[1] = rand.nextInt(rect[3] - rect[1] + 1) + rect[1];
return point;
}
}
Complexity
Constructor: Solution(int[][] rects)
- ⏰ Time complexity:
O(n log n)
- 🧺 Space complexity:
O(n)
Method: int[] pick()
- ⏰ Time complexity:
O(log n)
- 🧺 Space complexity:
O(1)
Method 3 - Reusing Area to Pickup the Point in Rectangle:
Also, we are saving rect[]
inside the map, rather than the index earlier. Here are the steps:
- Class Initialization:
- Define a
TreeMap<Integer, int[]> map
to map cumulative areas to rectangles. - Initialize
totalArea
to zero. - Create a
Random
objectrand
for generating random numbers.
- Define a
- Constructor:
- Iterate over each rectangle in the
rects
array:- Calculate the area of the rectangle.
- Add the rectangle’s cumulative area to
totalArea
. - Store the cumulative area as the key and the rectangle as the value in
map
.
- Iterate over each rectangle in the
- pick Method:
- Generate a random integer
randArea
between 0 andtotalArea - 1
. - Use
higherKey
to find the smallest key greater thanrandArea
inmap
. - Retrieve the rectangle associated with this key from
map
. - Compute the difference between the selected cumulative area and
randArea
. - Calculate the width of the rectangle.
- Determine random x and y coordinates within the rectangle using the difference.
- Generate a random integer
Code
Java
class Solution {
TreeMap<Integer, int[]> map = new TreeMap<>();
Random rand = new Random();
int totalArea;
public Solution(int[][] rects) {
this.totalArea = 0;
for (int[] rect : rects) {
// +1 as we need to consider edges also.
totalArea += (rect[2] - rect[0] + 1) * (rect[3] - rect[1] + 1);
map.put(totalArea, rect);
}
}
public int[] pick() {
int randArea = rand.nextInt(totalArea);
int key = map.higherKey(randArea);
int[] rect = map.get(key);
int diff = key - randArea - 1;
int width = (rect[2] - rect[0] + 1);
int x = rect[0] + diff % width;
int y = rect[1] + diff / width;
return new int[]{x, y};
}
}
Complexity
Constructor: Solution(int[][] rects)
- ⏰ Time complexity:
O(n log n)
- 🧺 Space complexity:
O(n)
Method: int[] pick()
- ⏰ Time complexity:
O(log n)
- 🧺 Space complexity:
O(1)