【C/C++】478. 在圆内随机生成点

491 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第22天,点击查看活动详情


题目链接:478. 在圆内随机生成点

题目描述

给定圆的半径和圆心的位置,实现函数 randPoint ,在圆中产生均匀随机点。

实现 Solution 类:

  • Solution(double radius, double x_center, double y_center) 用圆的半径 radius 和圆心的位置 (x_center, y_center) 初始化对象
  • randPoint() 返回圆内的一个随机点。圆周上的一点被认为在圆内。答案作为数组返回 [x, y]

提示:

  • 0< radius1080 < radius \leqslant 10^8
  • 107 xcenter,ycenter107-10^7 \leqslant x_center, y_center \leqslant 10^7
  • randPoint 最多被调用 31043 * 10^4 次

示例 1:

输入: 
["Solution","randPoint","randPoint","randPoint"]
[[1.0, 0.0, 0.0], [], [], []]
输出: [null, [-0.02493, -0.38077], [0.82314, 0.38945], [0.36572, 0.17248]]
解释:
Solution solution = new Solution(1.0, 0.0, 0.0);
solution.randPoint ();//返回[-0.02493,-0.38077]
solution.randPoint ();//返回[0.82314,0.38945]
solution.randPoint ();//返回[0.36572,0.17248]

整理题意

题目给定圆心坐标和圆的半径长度,要求每次 随机 返回一个坐标,返回的坐标要求在圆内,且圆周上的一点也被认为在圆内。

解题思路分析

观察题目描述得知该题与随机性有关,有很大可能会使用到随机函数 rand() 以及初始化随机函数的 srand((unsigned)time(NULL))

首先观察题目数据范围,根据题目输入数据类型和返回类型可知,输入和输出的坐标值类型是浮点数,且根据题目示例可知是需要保留小数点后 5 位,数据范围在 10810^8 以内,可以使用 double 进行存储。

  1. 我们可以先随机选择角度 [0, 360],再随机长度 [0, r],从而就可以确定一个随机点在圆上。
  2. 但是考虑到按照以上操作求得的随机坐标比较麻烦,我们可以换一种方法,扩大采样范围,再根据采样坐标是否在圆上来拒绝采样。

拒绝采样的意思是说:我们在一个更大的范围内生成随机数,并拒绝掉那些不在题目给定范围内的随机数,此时保留下来的随机数都是在范围内的。

  1. 想要使得采样点在半径为 r 的圆内,我们可以使用一个边长为 2r 的正方形覆盖住圆,并在正方形内生成均匀随机点,这样就可以只对于横、纵坐标分别生成一个范围在 [-r, r] 内的随机数即可。 微信截图_20220605155958.png
  2. 不断随机选取该范围内的点,直至点落在圆内。通过计算圆和正方形的面积比为 πR24R20.785\dfrac{\pi R^2}{4R^2} \approx 0.785,也就是随机选取点落在圆上的概率约为 78.5%78.5\%,期望的生成次数为 E()=10.7851.274=O(1)\text{E}(\cdot) = \dfrac{1}{0.785} \approx 1.274 = O(1),也就是平均随机选取 1.21.2 次即可落在圆上。

具体实现

  1. 首先我们把给定的圆心坐标看成偏移量,最后进行操作即可,首先以 (0, 0) 作为圆心,r 为半径进行随机取点操作。随机点的横、纵坐标 rxry 都在 [-r, r] 上随机选取。
  2. 判断如果当前坐标距离 (0, 0) 小于等于 r,表示随机点在圆内,对 rxry 进行偏移后输出即可,否则重复随机选取。
  • 浮点数的生成:采用随机生成 [0, 1] 之间的浮点数,表示百分比,利用百分比乘以需要的区间长度,再加上区间左端点即可。
  • 需要注意的是,该题需要一定常数优化,否则会 TLE 超时,在随机取点以及相关计算操作时需要注意优化,避免卡常。

复杂度分析

  • 时间复杂度:期望时间复杂度为 O(1)O(1)
  • 空间复杂度:O(1)O(1)

代码实现

class Solution {
private:
    double r, x, y, r2;
public:
    Solution(double radius, double x_center, double y_center) {
        //初始化随机函数
        srand((unsigned)time(NULL));
        r = radius;
        r2 = r * r;
        x = x_center;
        y = y_center;
    }
    
    vector<double> randPoint() {
        double rx, ry;
        //直到输出答案为止
        while(true){
            //使得随机rx 和随机ry 在 [-r, r]之间
            rx = -r + (double)(rand() / (double)RAND_MAX) * 2 * r;
            ry = -r + (double)(rand() / (double)RAND_MAX) * 2 * r;
            //如果在以(0,0)为圆心r为半径的圆内就套用到点(x, y)上即可
            if(rx * rx + ry * ry <= r2) return {x + rx, y + ry};
        }
        return {0, 0};
    }
};

/**
 * Your Solution object will be instantiated and called as such:
 * Solution* obj = new Solution(radius, x_center, y_center);
 * vector<double> param_1 = obj->randPoint();
 */

总结

  • 需要注意 随机浮点数 是如何生成,(rand() / (double)RAND_MAX) * (r - l) + l,表示选取随机百分比长度区间,加上左区间即可。
  • 拒绝采样 的思想和用法,扩大采样范围使得采样更为方便,同时选取符合要求的样本,其目的是为了方便采样操作,同时保证采样概率不变。
  • 该题还需要注意题目卡常的优化操作。
  • 测试结果: 微信截图_20220606015148.png

结束语

生活千种,人生百态,你怎样看待世界,世界就会怎样对待你。心有阳光的人,不但能给身边的人带去温暖,自己也会被这阳光滋养着。愿你带着一颗赤诚的心,朝着明亮的地方,用力生长。