The "Random Point in Non-overlapping Rectangles" problem asks you to design a data structure that, given a list of rects
(where each rectangle is described by its bottom-left and top-right integer coordinates), can efficiently pick a random integer point (x, y)
such that:
__init__(self, rects)
: Initialize the data structure with the given rectangles.pick()
: Randomly select and return a valid integer point [x, y]
from the rectangles, ensuring each point has an equal chance of being picked.[x1, y1, x2, y2]
, where (x1, y1)
is the bottom-left and (x2, y2)
is the top-right corner.
At first glance, the problem seems to invite a brute-force approach: generate all possible integer points in all rectangles, store them, and pick one at random. However, with large rectangles, the number of points grows quickly and storing them all is not feasible.
The key insight is that we do not need to explicitly list all points. Instead, we can:
The solution is built on the idea of mapping a random integer (from the total number of possible points) to a specific rectangle and then to a specific point inside that rectangle. Here's how it works step-by-step:
[x1, y1, x2, y2]
, the count is (x2 - x1 + 1) * (y2 - y1 + 1)
.k
between 0
and total_points - 1
.k
-th point.k
within that rectangle, and then calculate the exact (x, y)
coordinates using division and modulo arithmetic.
Suppose: rects = [[1,1,2,2],[3,3,4,4]]
k = 5
(0-based).k - 4 = 1
.x = 3 + (1 % 2) = 4
y = 3 + (1 // 2) = 3
(4, 3)
.
Brute-force Approach:
The main insight is to avoid storing all possible points by mapping a random index to a rectangle using prefix sums and binary search. This allows us to efficiently and uniformly pick a random integer point from the union of non-overlapping rectangles, regardless of their size or number. The approach is both time- and space-efficient, leveraging simple arithmetic and search techniques to guarantee uniform randomness.
import random
import bisect
class Solution:
def __init__(self, rects):
self.rects = rects
self.areas = []
area_sum = 0
for x1, y1, x2, y2 in rects:
area = (x2 - x1 + 1) * (y2 - y1 + 1)
area_sum += area
self.areas.append(area_sum)
def pick(self):
k = random.randint(1, self.areas[-1])
rect_index = bisect.bisect_left(self.areas, k)
x1, y1, x2, y2 = self.rects[rect_index]
width = x2 - x1 + 1
prev_area = self.areas[rect_index - 1] if rect_index > 0 else 0
offset = k - prev_area - 1
dx = offset % width
dy = offset // width
return [x1 + dx, y1 + dy]
class Solution {
vector<vector<int>> rects;
vector<int> areas;
public:
Solution(vector<vector<int>>& rects) : rects(rects) {
int area_sum = 0;
for (auto& r : rects) {
int area = (r[2] - r[0] + 1) * (r[3] - r[1] + 1);
area_sum += area;
areas.push_back(area_sum);
}
}
vector<int> pick() {
int k = rand() % areas.back() + 1;
int l = 0, r = areas.size() - 1;
while (l < r) {
int m = l + (r - l) / 2;
if (areas[m] < k) l = m + 1;
else r = m;
}
auto& rect = rects[l];
int x1 = rect[0], y1 = rect[1], x2 = rect[2], y2 = rect[3];
int width = x2 - x1 + 1;
int prev = l ? areas[l-1] : 0;
int offset = k - prev - 1;
int dx = offset % width;
int dy = offset / width;
return {x1 + dx, y1 + dy};
}
};
import java.util.*;
class Solution {
int[][] rects;
int[] areas;
Random rand = new Random();
public Solution(int[][] rects) {
this.rects = rects;
areas = new int[rects.length];
int areaSum = 0;
for (int i = 0; i < rects.length; i++) {
int[] r = rects[i];
int area = (r[2] - r[0] + 1) * (r[3] - r[1] + 1);
areaSum += area;
areas[i] = areaSum;
}
}
public int[] pick() {
int k = rand.nextInt(areas[areas.length - 1]) + 1;
int l = 0, r = areas.length - 1;
while (l < r) {
int m = l + (r - l) / 2;
if (areas[m] < k) l = m + 1;
else r = m;
}
int[] rect = rects[l];
int x1 = rect[0], y1 = rect[1], x2 = rect[2], y2 = rect[3];
int width = x2 - x1 + 1;
int prev = l > 0 ? areas[l-1] : 0;
int offset = k - prev - 1;
int dx = offset % width;
int dy = offset / width;
return new int[]{x1 + dx, y1 + dy};
}
}
var Solution = function(rects) {
this.rects = rects;
this.areas = [];
let areaSum = 0;
for (let r of rects) {
let area = (r[2] - r[0] + 1) * (r[3] - r[1] + 1);
areaSum += area;
this.areas.push(areaSum);
}
};
Solution.prototype.pick = function() {
let k = Math.floor(Math.random() * this.areas[this.areas.length - 1]) + 1;
let l = 0, r = this.areas.length - 1;
while (l < r) {
let m = Math.floor((l + r) / 2);
if (this.areas[m] < k) l = m + 1;
else r = m;
}
let rect = this.rects[l];
let x1 = rect[0], y1 = rect[1], x2 = rect[2], y2 = rect[3];
let width = x2 - x1 + 1;
let prev = l > 0 ? this.areas[l - 1] : 0;
let offset = k - prev - 1;
let dx = offset % width;
let dy = Math.floor(offset / width);
return [x1 + dx, y1 + dy];
};