Random Point in Non-overlapping Rectangles

Med
#0484Time: - **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.

Prompt

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 rectangles rects.
  • 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 <= 100
  • rects[i].length == 4
  • -109 <= ai < xi <= 109
  • -109 <= bi < yi <= 109
  • xi - ai <= 2000
  • yi - bi <= 2000
  • All the rectangles do not overlap.
  • At most 104 calls will be made to pick.

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 totalPoints to 0.
    • For each rectangle rect in rects:
      • Calculate the number of points: count = (rect[2] - rect[0] + 1) * (rect[3] - rect[1] + 1).
      • Add count to pointCounts.
      • Add count to totalPoints.
  • Pick Method (int[] pick()):
    • Generate a random integer target between 1 and totalPoints.
    • Iterate through the pointCounts from index i = 0 to N-1:
      • If target is less than or equal to pointCounts[i], this means the point falls into the current rectangle i. Select this rectangle and break the loop.
      • Otherwise, subtract pointCounts[i] from target and continue to the next rectangle.
    • Once a rectangle [a, b, x, y] is chosen:
      • Generate a random x-coordinate u in the range [a, x].
      • Generate a random y-coordinate v in the range [b, y].
    • Return the point [u, v].

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.

  1. We pick a random integer target from 1 to totalPoints. This target conceptually represents the k-th point if all points from all rectangles were laid out in a single sequence.
  2. We then iterate through our list of point counts. For each rectangle, we check if our target falls within the range of points contributed by that rectangle. We do this by seeing if target is 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 from target and move to the next one.
  3. 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 if pick() is called many times with a large N.

Solutions

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(); */

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.