发信人: runformore (奎罗伊), 信区: Mathematics

标 题: 问个谷歌面试的问题

发信站: 水木社区 (Mon Feb 1 03:51:24 2016), 站内

1维,1米长的路面,每次下一滴雨,每滴雨落到地面上长度是0.01米,落点假设均匀分布,求问下了多少滴雨之后路面会全部湿透,求期望?

这个题目有一处说的不严谨,需要特别解释一下。

雨滴落点假设均匀分布这句话应该理解为每个雨滴的中心均匀分布在1米长的路面上,也就是雨滴实际可以覆盖的长度是 1.01 米(两边都可以多出 0.005 米来)。

这个问题刚一看可能觉得挺简单的。但是仔细算算就发现真的很难。水木数学版上大家讨论了几天也只是得到了个无穷级数解。至今没有人能给出个有限项的闭式解来。这个无穷级数解是 dragonheart6 给出的,推导过程如下。
这里写图片描述
(dragonheart6 是水木社区 IQDoor 版的版主,智商超高。每次看他的帖子都有种智商被无情碾压的感觉)

关于这个推导这里不多解释。今天想说一说这个问题如何用编程用数值仿真实验来估算结果。

这个问题是个用 N 个小区间来覆盖一个大区间的概率问题。我们的区间都是定义在实数域上的。理论上来说有无穷多种小的区间,有无穷多种覆盖方式。模拟这个问题就有两种思路:

  1. 将大区间分成有限个小区间,假设每一滴雨都会覆盖连续的几个小区间。这样实际上就是把连续问题离散化了。这样处理编程会简单很多,但是这些小区间的份数的选择是个问题,份数太少计算出的结果会与原问题的结果有很大的出入。份数过多计算量会非常大。而这个份数的选取只能靠做一些数值实验来确定。

  2. 定义一个数据结构来表示区间,直接进行区间之间的运算。对于这个问题来说其实只有一种关键的运算,就是相交区间的合并运算。当这些小的区间(雨滴)合并为一个能够覆盖 [0, 1] 的区间后计算计数就可以停止了。

第一种思路编程实现很简单。没必要多说。这里主要介绍第二种思路的编程实现。我用到的编程语言是 C++,用 C++ 写这种代码还是挺麻烦的,如果用 Python 一类的语言可以写的更简单些。不过考虑到蒙特卡洛仿真程序属于典型的计算密集型程序。考虑到运算速度最终还是选择了 C++。

首先,我们定义了一个结构体来表示区间。

struct Interval
    Interval(double l, double u): low(l), up(u) {}
    inline void enlarge(Interval b);
    double low;
    double up;
// 试着将两个区间合并为一个区间
void Interval::enlarge(Interval b)
    if(b.low < low)
        low = b.low;
    if(b.up > up)
        up = b.up;

其中的 enlarge() 用来将两个区间扩充为一个大区间,这个扩充操作不需考虑两个区间是否相交。只是简单的扩展操作就可以。

由于我们要操作许多个区间,要经常进行区间的插入与删除操作。为了方便,我们用一个链表来存储这些区间。并且保证区间的下边界是升序排列。这里没有使用 STL 中的链表,用的是 Qt 里面的实现。这个链表的样子如下:

QLinkedList<Interval> list;

升序排列是靠插入时放到合适的位置来保证的。

// 将区间插入到 List 中,只要保证区间的下边界是升序排列就可以了
void insert(QLinkedList<Interval> &list, Interval interval)
    if(list.empty())
        list.append(interval);
        return;
    QLinkedList<Interval>::iterator iter;
    for (iter = list.begin(); iter != list.end(); ++iter)
        if(iter->low >= interval.low)
            list.insert(iter, interval);
            return;
    list.append(interval); // 到这里表示所有的现有区间的下边界都比这个区间的小,因此将和这个区间插入到最后

之所以要求这些区间的下边界是升序的,是为了能够方便的计算哪些区间可以合并,并快速的合并这些区间。下面的代码就是用来合并有交集的区间的,很简单,就是依次判断链表中相邻的两个区间是否有公共部分,有的话就合并。没有的话就去判断下一对区间。合并之后这些区间的下边界还是升序排列的。

// 合并相邻区间,要求区间的下边界是增序排列的 bool merge(QLinkedList<Interval> &list) QLinkedList<Interval>::iterator current; QLinkedList<Interval>::iterator next; for (current = list.begin(); current != list.end(); ++current) next = current + 1; while(next != list.end() && current->up >= next->low) current->enlarge(*next); list.erase(next); next = current + 1; return list.count() == 1;

这个函数的返回值是布尔类型,当 list 中只剩下一个区间时返回真,否则返回假。

之所以要这样设计,是因为我们会预先往这个list 中放两个区间。分别是 [-1, 0] 和 [1, 2]。 那么通过 merge 操作能够合并为一个区间时肯定就是雨滴完整的覆盖了[0, 1] 这个区间了。
还要有个随机生成一个区间(雨滴)的函数。

Interval rain_drop(double range) double center = static_cast<double> (rand()) / RAND_MAX; double min = center - range; double max = center + range; return Interval(min, max);

有了这几个函数就可以进行我们的数值实验了。我们将一次实验写为一个函数。

int test()
    QLinkedList<Interval> list;
    insert(list, Interval(-1, 0));
    insert(list, Interval(1, 2));
    int n = 0;
        insert(list, rain_drop(0.005));
    }while( !merge(list) );
    return n;

最后是主程序,我们计算了 50000 次实验的结果。将其取平均值。

int main(int argc, char *argv[]) double n = 0; std::srand((unsigned int)(std::time(NULL))); QTime t; t.start(); for(int i = 0; i < 50000; i++) n += test(); int tm = t.elapsed(); n /= 50000.0; std::cout << "n = " << n << std::endl; std::cout << "Time elapsed: " << tm / 1000 << " s" << std::endl;

计算结果如下:

n = 726.782
Time elapsed: 75 s
                    水木社区上看到的一道概率题原帖是这样的:  发信人: runformore (奎罗伊), 信区: Mathematics    标  题: 问个谷歌面试的问题    发信站: 水木社区 (Mon Feb  1 03:51:24 2016), 站内    1维,1米长的路面,每次下一滴雨,每滴雨落到地面上长度是0.01米,落点假设均匀分布,求问下了多少滴雨之后路面会全部湿透,求期望?这
把一米长的路面分成100个格子,每个格子都落了雨滴了那么路面就湿透了。随机性在于每次哪个格子落雨是不确定的。 分析到这,了解优惠券收集问的人应该知道这其实可以归约到该问的。那么优惠券收集问是怎样的呢?
    1)采用两层模型(人群画像*人群转化):新生儿出生数=Σ各年龄层育龄女性数量*各年龄层生育比率
    2)从数字到数字:如果有前几年新生儿出生数量数据,建立时间序列模型(需要考虑到二胎放开的突变事件)进行预测
    3)找先兆指标,如婴儿类用品的新增活跃用户数量X表示新生儿家庭用户。Xn/新生儿n为该年新生儿家庭用户的转化率,如...
				
: 在闭区间[0, 1]内,我们随机取出两点(服从均匀分布)A和B,形成一个新的闭区间[min{A,B}, max{A,B}]。如此反复n次,我们就有了n个随机闭区间。那么这n个闭区间不出现重叠的概率是多大呢? 将问可以简单转化为2n个值的抽样问,首先将2n个值进行排序 x1<x2<x3<x4<····<x2n-1<x2n 则符合n个闭合区间不重叠的一种...
解决方法: 在unix环境下,小于1024的端口不能被普通用户绑定,只能由有root权限的用户来进行绑定,可是使用sudo命令也并不起作用, 所以需要绑定一个大于1024的端口,最终问得到解决。 修改app.py中的端口80为9002,问解决 public void Test2(){ Date time =new Date("Tue Mar 26 00:00:00 CST 2019"); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); String ti... 就出现了这样的警告: Mon Jul 23, 2018 14:38:55: Failed to initialize communcation with hardware: SWIM error [30200]: ST-Link connection error  Mon Jul 23, 2018 14:38:55: Failed ...
说明 js收到数据var a="Tue Sep 03 2019 00:00:00 GMT+0800 (中国标准时间),Sun Oct 06 2019 00:00:00 GMT+0800 (中国标准时间)" 我要把两个时间进行分割,然后转换成时间戳,比较大小,如果后者小于前者,就会提示; 然后再把时间戳变成标准的yyyy-MM-dd的格式 1、分割data varsz=...
异常:This application has no explicit mapping for /error, so you are seeing this as a fallback解决方法
首先,需要使用Python爬虫爬取水木社区的帖子信息。可以使用Python中的requests库或者Scrapy框架来实现。爬虫需要爬取每个帖子的标、内容、发帖时间等信息。需要注意的是,为了避免被封IP,需要设置合理的爬虫策略,比如限制访问频率、使用IP代理等。 接下来,需要对爬取到的帖子信息进行处理,包括提取帖子的发帖时间、统计每个帖子的回复数量等。可以使用Python中的正则表达式或者BeautifulSoup库来实现。 然后,需要对帖子信息进行排序,可以按照发帖时间或者回复数量来排序。需要注意的是,如果按照回复数量来排序,需要考虑帖子的发帖时间,避免出现因为时间差异而导致排序结果不准确的情况。 最后,可以进行顶贴实验。先记录下当前的帖子排序结果,然后对某个帖子进行顶贴操作,再次获取帖子排序结果。比较两次排序结果的差异,可以看出顶贴对网站内容排序的影响。 需要说明的是,顶贴对网站内容排序的影响不是绝对的,它受到多方面的影响,比如帖子发帖时间、回复数量、用户行为等。因此,在测试过程中需要尽可能地控制其他因素,以保证测试结果的准确性。