持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第15天,点击查看活动详情
题目描述
给定一个由非重叠的轴对齐矩形的数组 rects ,其中 rects[i] = [ai, bi, xi, yi] 表示 (ai, bi) 是第 i 个矩形的左下角点,(xi, yi) 是第 i 个矩形的右上角点。设计一个算法来随机挑选一个被某一矩形覆盖的整数点。矩形周长上的点也算做是被矩形覆盖。所有满足要求的点必须等概率被返回。
在给定的矩形覆盖的空间内的任何整数点都有可能被返回。
请注意 ,整数点是具有整数坐标的点。
实现 Solution 类:
- Solution(int[][] rects) 用给定的矩形数组 rects 初始化对象。
- int[] pick() 返回一个随机的整数点 [u, v] 在给定的矩形所覆盖的空间内。
示例 1:
输入:
["Solution", "pick", "pick", "pick", "pick", "pick"]
[[[[-2, -2, 1, 1], [2, 2, 4, 6]]], [], [], [], [], []]
输出:
[null, [1, -2], [1, -1], [-1, -2], [-2, -2], [0, 0]]
解释:
Solution solution = new Solution([[-2, -2, 1, 1], [2, 2, 4, 6]]);
solution.pick(); // 返回 [1, -2]
solution.pick(); // 返回 [1, -1]
solution.pick(); // 返回 [-1, -2]
solution.pick(); // 返回 [-2, -2]
solution.pick(); // 返回 [0, 0]
思路
整体思路是,先随机获取在那个矩形内,再按照顺序找到点。因为矩形大小不一样,所以随机获取在哪个矩形内是需要带权重的随机。
这里可以用前缀和+二分查找的方式实现,假设有len个矩形,每个矩形内点的数量为num[0]~num[len-1],那么每个矩形的权重就是num[i],设sum=sum(num[i])
我们在[0,sum[i])中随机或者随机值r之后,需要定位到在哪个矩形中,利用前缀和,这里应该在第一个preSum[i]大于等于(r+1)的矩形中
定位到了哪个矩形i,再定位x、y就比较简单了。
这个点在这个矩形中的序号是r-preSum[i-1],记为pointIndex,这个矩形中,每行的点数量为(rects[i][2] - rects[i][0] + 1)
记为row,那么点的坐标就是[rects[i][0] + pointIndex%row, rects[i][1] + pointIndex/row]
另外,每个矩形内整数点的数量为(x2 - x1 + 1) * (y2 - y1 + 1)
Java版本代码
class Solution {
private int[][] rects;
List<Integer> preSumList;
Random random;
public Solution(int[][] rects) {
random = new Random();
this.rects = rects;
// 初始化前缀和数组
preSumList = new ArrayList<>();
for (int i = 0; i < rects.length; i++) {
int x1 = rects[i][0];
int y1 = rects[i][1];
int x2 = rects[i][2];
int y2 = rects[i][3];
int num = (x2 - x1 + 1) * (y2 - y1 + 1);
if (i == 0) {
preSumList.add(num);
} else {
preSumList.add(preSumList.get(i-1) + num);
}
}
}
public int[] pick() {
int rv = random.nextInt(preSumList.get(preSumList.size() - 1));
int boxIndex = search(rv + 1);
int pointIndex = rv;
if (boxIndex > 0) {
pointIndex -= preSumList.get(boxIndex-1);
}
// 每行有多少个点
int row = rects[boxIndex][2] - rects[boxIndex][0] + 1;
return new int[]{rects[boxIndex][0] + pointIndex%row, rects[boxIndex][1] + pointIndex/row};
}
/**
* 二分查找第一个大于等于target的位置
* @param target
* @return
*/
private int search(int target) {
int left = 0, right = preSumList.size()-1;
while (left < right) {
int mid = left + ((right - left) >> 1);
int num = preSumList.get(mid);
if (num < target) {
left = mid + 1;
} else {
right = mid;
}
}
return left;
}
}