问题如上。

这是我被面试的一个题目。

我的第一反应给出的解决的方法是。开启  n 个线程并标记序号,各个线程打印出它的序号。直到有 m 个线程被调度时,停止全部线程。

打印出的序号即是 m 个等概率出现的数字。

面试官听到这个解决的方法,吸了一口凉气。预计心里在想,这小伙疯了!我当时自知这个解决方式不是面试官想要的。于是说了,假设这个 n 非常大,那么就要另

想办法了,由于不可能在一个进程里产生随意多个线程。

想啊想,过了两分钟。还是没有找到解决的方法。面试官非常 nice 让我回去后再想一想。

事实上这个题细想。有些难度。你可能有一种思路:假设能一次性取出 m 个数字。再保证各数字是随机的。则能够满足等概率。但。

。。

怎样一次性取出 m 个数字呢?

一次性生成多个随机数?假设生成的数字有同样则不是等概率的。由于这个数字比其他的数字出现的次数多,则概率大些(虽然你忽略了它出现多次)。

或者还有思路:

每次出一个,保证取 m 次得到的各数字概率相等。听起来。似乎这样的思路要难些。

[解法一]

我们来模拟这个过程。设有一个长度为 m 的辅助数组 B,用来装选中的数字。数组末满时,依次从长度为 n 的数组 A 中每次取一个数字顺序放入 B 中。直到 B 满了。

设对于兴许的每个元素,其装入 B 中的概率为 x。此元素装入 B 中的操作是将它与 B 中的某个元素置换。

则 B 中已经存在的某个元素,继续在 B 中存在的概率为:(1-x) + x*(m-1)/m。即当前取出的元素不进入 B。被直接舍弃,或者当前取出的元素进入 B ,但置换不发生在这个元素身上。

当前 A 中取出的元素。在 B 中存在的概率为 x。即,仅仅要这个元素被选中,就一定会进入 B。

因为要使用每一个元素的概率相等。则有:

x = (1-x) + x*(m-1)/m。故 x = m/(m+1)。

也即。按上面的操作便能保证每个被留下来的元素的概率相等且为 x ,即 m/(m+1)。

[解法二]

事实上。我们考虑一下,这个模型不就是抽奖的模型吗,有 n 张彩票,n 个人每人一张,怎样选出 m 个人出来中奖。即。我们仅仅须要模拟一个公正的抽奖过程便能得到等概率的 m 个人。

我们都知道,抽奖不分先后。每一个人中奖的机率都一样。因此。最简单的做法是将 n 个人随机化排成一列,再取前 m 个人中奖就可以。

那么,我们借助洗牌算法便能做到。那么,怎样得到一个好的洗牌算法呢?一个能够证明是均匀的方法例如以下:

对于第 i 张牌,它以 i/(i+1) 的概率与前面 i 张牌交换,实际操作时,能够生成一个 0 ~ i 之间的随机数。当其不为 i 时运行交换。交换的操作是:将此牌与前面 i 张牌随机交换。

于是,能够证明。第 i 张牌在位置 i ,也即。它没有发生交换的概率为 1 - i/(i + 1)= 1/(i+1)。

第 i 张牌在前面不论什么一个位置的概率为 i/(i+1)*1/i = 1/(i+1) 。可是,我们还须要证明,前 i 张牌中的随意一张在前面
i 个位置中的随意一个位置的概率为 1/(i+1) 才算是证明全然。假设直接入手。这个证明能够想象是相当复杂的。我们使用数学归纳法证明,按上面的操作,随意第 i 张牌被操作完毕后,总共的 i + 1 张牌中的随意一张在0 ~ i 的随意一个位置上的概率为 1/(i + 1)。

证明:

当仅仅有一张牌(第 0 张牌)时,在位置 0 上的概率为 1。

如果第 i 张牌被操作完毕后。总共的 i + 1 张牌中的随意一张在0 ~ i 的随意一个位置上的概率为 1/(i + 1)。

则对于第 i + 1 张牌被操作后:

前面已经证明过。第 i+1 张牌放置在 0 ~ i+1 中的任何位置的概率为 1/(i+2)。

对于 0 ~ i 中的随意一张牌 x,它原先在 0 ~ i 上任何位置的概率为 1/(i + 1)。

x 被换到第 i+1 位置的概率为 (i+1)/(i+2) * 1/(i+1) = 1/(i+2)。x 如今还在 0 ~ i 位置的概率即 1 减去前者,为 : 1 - 1/(i+2)。

而 0 ~ i 共同拥有 i + 1 个位置,故 x 在随意一个位置的概率为:

(1 - 1/(i+2))*1/(i+1) 结果为 1/(i+2)。

于是就证明原结论。因此。这是一个平衡的洗牌算法。

[解法三]

假设我们每次在面临第 i 个元素,不是像解法一中的,维持一个固定的概率去决定该元素是否留下,而是与当前已处理过的元素个数相关,是否能得到一个解法呢?

设总共已处理的个数为 N。当前正要处理的是第 i 个元素。辅助数组为 B。源数组为 A。操作例如以下:

1.当 i <= m 时。元素留下。

2.当 i > m 时,使用概率 m/N 决定元素的去留。假设元素留下。则它随机与 B 中某个元素 x 置换(丢弃x,保留该元素)

证明等概率:

1.当 i = m + 1 时,此元素留下的概率为 m/(m+1)。B 中随意一个元素留下的概率为:1-m/(m+1) + m/(m+1)*(m-1)/m = m/(m+1)。故此时,B 中全部元素的概率为 m/(m+1)。

2.设当已处理 N 个元素时,B 中的元素的概率为 m/N。

则当已处理 N+1 个元素时,当前元素留下的概率为 m/(N+1)。B 中随意一个元素留下的概率为:m/N*(1-m/(N+1) + m/(N+1)*(m-1)/m) = m/(N+1)。最前面的 m/N 表示此元素在 B 中,否则不在 B 中。

故使用上面的操作方法。留下的元素的概率是相等的。且与总共处理的元素的个数是相关的。

这一模型和解法一中的模型是不同样的。注意差别:

解法一中的模型适用于保留下来的元素概率相等。且永远不变。

解法二中的模型适用于保留下来的元素概率相等。但随着处理的元素的个数添加而改变,这也意味着,当须要从未知数目的数据源中取 m 个数字,使其等概率,这样的方法是很适用的。

[解法四]

我们已经有一个心得了,解法方案好像类似于:面临当前元素时。使用一个概率(这个概率可能是动态变化的。或者不变的)决定去留,若留,则与某个已选择的元素置换。以下再给出一种方法。

设 A 为源数组。B 为辅助数组(装入已选择的元素)。A 长度为 n。B 长度为 m。须要从 A 中取 m 个数字放入 B。使它们等概率。

遍历 A,在面临第 i 个元素 x 时,记 p 为还须要从 A 中选出的元素个数。q 为从 x 向后数,将 A 数完的个数。包含 x。决定 x 被选中的概率设置为 p/q。这也能够达到等概率。

1:第 0 个元素被选中的概率为 :m/n

2:第 1 个元素被选中的概率为 :m/n*(m-1)/(n-1) + (1-m/n)*m/(n-1) = m/n

3:第 2 个元素被选中的概率为:... = m/n

....

依此类推,不管哪个元素被选中的概率都为 m/n。以下,我们证明随意一个元素被选中的概率都为 m/n。

假设按上面的思路去证明将非常复杂。可是有一个非常巧妙的证明方法。

我们看这个问题的模型,实际上,它就是一个抽奖模型,如今有一个箱子里面装着 n 张奖券,写着“中”。或“不中”,当中。写着“中”的有 m 张,如今问。第 k 次抽奖,中奖的概率为

多少?这显然为 m/n!

还记得 "抽奖与顺序无关” 吗?于是。我们独立写出第 k 次中奖的概率的表达式:

C(m,1)*A(n-1,m-1) / A(n,m) = m/n。

故,上面的操作方法,随意一个元素被选中的概率都为 m/n。

解法四是对抽奖的全过程进行概率模拟,而解法二是对抽奖的前置处理进行模拟。

此解法的模型适用于保留下来的元素概率固定且相等。

从 n 个数字中选出 m 个不同的数字,保证这 m 个数字是等概率的的更多相关文章

  1. 查找n个数字中的最大值

    闲来无事,试试用arg_list查找n个数字中的最大者. 又因为本人喜欢模板, 所以就早早的写了以下代码, 没有经过严格测试. /*********************************** ...

  2. 小易邀请你玩一个数字游戏,小易给你一系列的整数。你们俩使用这些整数玩游戏。每次小易会任意说一个数字出来,然后你需要从这一系列数字中选取一部分出来让它们的和等于小易所说的数字。 例如: 如果{2,1,2,7}是你有的一系列数,小易说的数字是11.你可以得到方案2+2+7 = 11.如果顽皮的小易想坑你,他说的数字是6,那么你没有办法拼凑出和为6 现在小易给你n个数,让你找出无法从n个数中选取部分求和

    小易邀请你玩一个数字游戏,小易给你一系列的整数.你们俩使用这些整数玩游戏.每次小易会任意说一个数字出来,然后你需要从这一系列数字中选取一部分出来让它们的和等于小易所说的数字. 例如: 如果{2,1,2 ...

  3. 一串数字中,只有一个数字出现一次,其他数字都出现两次,查找出这个数字(python)(原创)

    背景: 电话面试&手撕代码 2019.03.22 Mufasa 问题: 一串数字中,只有一个数字出现一次,其他数字都出现两次,查找出这个数字 条件: 这串数字是有序数 解决方法: 核心代码只有 ...

  4. 谷歌笔试题--给定一个集合A=[0,1,3,8](该集合中的元素都是在0,9之间的数字,但未必全部包含), 指定任意一个正整数K,请用A中的元素组成一个大于K的最小正整数。

    谷歌笔试题--给定一个集合A=[0,1,3,8](该集合中的元素都是在0,9之间的数字,但未必全部包含), 指定任意一个正整数K,请用A中的元素组成一个大于K的最小正整数. Google2009华南地 ...

  5. 为什么Java7开始在数字中使用下划线

    JDK1.7的发布已经介绍了一些有用的特征,尽管大部分都是一些语法糖,但仍然极大地提高了代码的可读性和质量.其中的一个特征是介绍字面常量数字的下划线.从Java7开始,你就可以在你的Java代码里把长 ...

  6. 一道经典的面试题:如何从N个数中选出最大(小)的n个数

    转载:https://zhidao.baidu.com/question/1893908497885440140.html 这个问题我前前后后考虑了有快一年了,也和不少人讨论过.据我得到的消息,Goo ...

  7. “全栈2019”Java第十六章:下划线在数字中的意义

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...

  8. day04_03 题目判断三个数字中的最大值

    num1 = input("Num1:") num2 = input("Num2:") num3 = input("Num3:") 输出三个 ...

  9. SQL Server 从一组数字中随机获取一个数

    很多人在开发需求中想获取一个随机数,或者从一组数字中获取一个数, 这个需求很简单,而且有很多方式可以实现,下面就介绍几种常见的方式,以作为笔记或供有需要的人参考. 比如有一组数字: 57 59 63 ...

随机推荐

  1. 一步一步学习IdentityServer3 (10)

    在某些服务器环境下 identityserver3 会闹情绪, 比如在google浏览器下授权失败(陷入死循环) 查了很多资料好像然并卵 Microsoft.Owin.Security.Notific ...

  2. CF474D. Flowers

    D. Flowers time limit per test 1.5 seconds memory limit per test 256 megabytes input standard input ...

  3. JSON解析代码

    /** * 解析有数据头的纯数组 */ private void parseHaveHeaderJArray() { //拿到本地JSON 并转成String String strByJson = J ...

  4. Bootstrap Paginator分页插件使用示例

    最近做的asp.netMVC项目中需要对数据列表进行分类,这个本来就是基于bootstrap开发的后台,因此也就想着bootstrap是否有分页插件呢,或者说是基于jquery支持的分页功能,这样整体 ...

  5. 【LOJ】#2031. 「SDOI2016」数字配对

    题解 这个图是个二分图,因为如果有一个奇环的话,我们会发现一个数变成另一个数要乘上个数不同的质数,显然不可能 然后我们发现这个不是求最大流,而是问一定价值的情况下最大流是多少,二分一个流量,加上一条边 ...

  6. Ruby windows7安装配置(最新版本)

    1.下载最新版本的rubyinstaller并安装http://rubyinstaller.org/downloads/ 如下图所示设置路径,我安装时将所有选项都打够了,免除了后面需要什么配置麻烦. ...

  7. ThinkPHP join() table()方法的使用,多表查询

    ThinkPHP模型类​比较常用的两个方法,table() join() table 1 $list = M()->table('user1 a, user2 b')->where('a. ...

  8. php中explode和implode函数

    explode array explode ( string $delimiter, string $string, [ , $limit ] ) 函数返回由字符串组成的数组,每个元素都是string ...

  9. 20169211《Linux内核原理与分析》课程总结

    第一周作业:linux入门学习:熟悉操作linux的基础命令 第二周作业:实验反汇编一个简答的C程序,学习汇编代码的工作过程 第三周作业:学习linux内核的启动过程 第四周作业:学习linux内核进 ...

  10. PHP会话——模拟购物车的功能

    1.php默认是不开启会话的,要使用会话用两种方法:(1)使用session_start();显示的开启会话.(2)在php.ini中找到如下的一行:找到session.auto_start = 0, ...