leetcode-非重叠矩形中的随机点

135 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 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:

497.jpg 输入:
["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;
    }
}