昨天被这道题整破防了,所以没写博客,今天补上
【前缀和+二分查找】通过矩形的面积来确定落在哪一个rect中,然后根据他在矩形中第几个点来确定具体的坐标。
例如四个矩形大小分别为2,4,2,6
计算前缀和后2,6,8,14 然后我们随机取一个[0, 14)的数,点0-1落在第一个矩形内,点2-5落在第二个矩形内,点6-7落在第三个矩形内,点8-13落在第四个矩形内,这样我们发现对应的矩形下标就是>这个数的最小值:
0-1 大于他们的第一个数是2,下标为0
2-5 对应6,下标1
6-7 对应8,下标2
所以通过二分查找来查第一个大于这个随机数的下标就是我们随机选择到的矩形了,然后把随机数减去前面的前缀和得到这个点处于这个矩形中的第几个点,然后根据矩形的长和左下角坐标进行还原就行了。
class Solution {
int[][] r;
int n;
int[] pre;
Random random = new Random();
public Solution(int[][] rects) {
n = rects.length;
r = rects;
pre = new int[n];
int i = 0;
for(var r: rects){
if(i == 0) pre[i] = (r[2] - r[0] + 1) * (r[3] - r[1] + 1);
else pre[i] = pre[i - 1] + (r[2] - r[0] + 1) * (r[3] - r[1] + 1);
i++;
}
}
public int[] pick() {
int left = 0, right = n - 1;
int rd = random.nextInt(pre[right]);
while(left <= right){
int mid = (left + right) >>> 1;
if(pre[mid] > rd) right = mid - 1;
else left = mid + 1;
}
if(left != 0) rd -= pre[left - 1];
int y = (r[left][3] - r[left][1] + 1);
return new int[] { r[left][0] + rd / y, r[left][1] + rd % y};
}
}
题解中对于前缀和加入了前导0,我们再看一下这种前缀和如何做,还是刚才那四个矩形2,4,2,6
计算前缀和后为0,2,6,8,14,产生的随机数rd仍是是[0, 14)范围内的,
0-1 依然需要对应下标0,在这个前缀和数组中是找<=rd的最大值,即0
2-5 需要对应下标1,也就是<=rd的2,(下一个6就大于5)了
这样来说就是在这个前缀和数组中查找<=rd的最大值
其实我们不难发现,当数字固定在[0, 14)范围内时,查找(2,6,8,14)中大于rd的最小值和查找(0,2,6,8,14)中小于等于rd的最大值所找的下标是一样的
class Solution {
int[] pre;
int n;
int[][] r;
Random random = new Random();
public Solution(int[][] rects) {
r = rects;
n = r.length;
pre = new int[n + 1];
int i = 1;
for(var re: rects){
pre[i] = pre[i - 1] + (re[3] - re[1] + 1) * (re[2] - re[0] + 1);
i++;
}
}
public int[] pick() {
int left = 0, right = n;
int rd = random.nextInt(pre[n]);
while(left <= right){
int mid = (left + right) >>> 1;
if(pre[mid] > rd) right = mid - 1;
else left = mid + 1;
}
int i = right;
rd -= pre[i];
int y = r[i][3] - r[i][1] + 1;
return new int[] {r[i][0] + rd / y, r[i][1] + rd % y};
}
}
这里再对二分做一个总结吧:
我们看到判定条件pre[mid] > rd的时候往左找,说明右边全是pre[mid]>rd的,左边都是pre[mid]<=rd的,所以左边的右端点就是<=rd的最大值。