Java 随机数获取方法:
1 |
|
更多 Java 基础数学函数参见:站内文章【索引】Java 代码刷题热身。
给你一个单链表,随机选择链表的一个节点,并返回相应的节点值。每个节点被选中的概率一样。
在这个题目的场景中,链表是已知的,相当于在一个静态数据中随机选择链表结点。我们直接想到就是把链表中的元素都放到可以随机访问的数据结构中,如 ArrayList,再通过随机数的生成进行随机选择。
1 |
|
复杂度分析:
我们可不可以将空间复杂度降到 呢?我们可以使用蓄水池采样算法。咱们先看代码:
1 |
|
对于每次选取,我们都需要遍历所有的元素。假设最终被选中的是第 k 个元素 ,这就意味着:
因此,对于某个序号为 k 的元素,假设其被选中,则概率为:
所以列表中的每个元素,均会以 的概率被选取。
其实如果静态数据场景,要求不使用额外空间的话,可以试试随机选一个数代表链表第 n 个结点,在遍历 n 次。复杂度跟蓄水池算法差不多。
1 |
|
相关题目:
在上一小节的题目中,我们通过时间和空间的权衡,写了两种解决思路。实际上对于静态数据,每次获取随机数的时间复杂度为 的话效率实在有点低。就比如在 398. 随机数索引 - 力扣(LeetCode) 就不能使用蓄水池算法。
蓄水池采样更适合动态数据场景,如:
随机选择未知长度数据流中的某个数据
给出一个数据流,这个数据流的长度很大或者未知。并且对该数据流中数据只能访问一次。请写出一个随机选择算法,使得数据流中所有数据被选中的概率相等。
例如:
算法的过程:
一个简单的证明(当采样的数量为 1 时)已经在上一节中阐述了。下面说明当采样数量为 k 时的证明方式:
所以对于其中每个元素,被保留的概率都为 。
给你一个 下标从 0 开始 的正整数数组 w ,其中 w[i] 代表第 i 个下标的权重。
请你实现一个函数 pickIndex ,它可以 随机地 从范围 [0, w.length - 1] 内(含 0 和 w.length - 1)选出并返回一个下标。选取下标 i 的 概率 为 w[i] / sum(w) 。
对于这道题目,使用「时空权衡」小节中的方法并不能通过测试用例,要么 TLE 要么 MLE。

1 | class Solution { |
给定一个整数 n 和一个 无重复 黑名单整数数组 blacklist 。设计一种算法,从 [0, n - 1] 范围内的任意整数中选取一个 未加入 黑名单 blacklist 的整数。任何在上述范围内且不在黑名单 blacklist 中的整数都应该有 同等的可能性 被返回。
优化你的算法,使它最小化调用语言内置随机函数的次数。
通过映射,把可以进行随机的区间进行集中。
先思考一个简单的情形。假设黑名单长度为 m,且 里都是白名单中的数, 中的数都处在黑名单。那么我们的算法就是从 中随机选一个数出来就行。
在一般情况下,黑名单中的数随机分布。那么我们可以把 里处于黑名单中的数,映射到 中白名单的数就行,然后从 中随机选一个数出来作为返回结果。
1 | class Solution { |
等概率产生 0 和 1
有一个随机数发生器 f(),以概率 产生 0,概率 产生 1,请问能否利用这个随机数发生器,构造出新的发生器 g(),以 的概率产生 0 和 1 。
不能用已有的随机生成库函数。
思路:我们可以通过随机数发生器 f(),制造一些等概率事件,并定义这些事件所表示的含义。
步骤:
f(),得到以下事件的概率:
0;11 | public int g() |
等概率产生 1 到 N 之间的数
有一个随机数发生器 f(),以概率 产生 0,概率 产生 1,请问能否利用这个随机数发生器,构造出新的发生器 h(),等概率产生 1~N 。
在上一部分中,我们构造了新的发生器 g() 以 的概率产生 0 和 1 。假设 N 的二进制表示的位数为 k,那么我们可以调用 k 次 g(),每次 g() 生成的的结果组合成一个新的数。生成数的范围为 ,每个数被生成的几率相等。注意到,题目要求的 1~N 必然会在这个范围当中。如果生成的数符合输出要求则直接返回,否则重新生成。
给定方法 rand7 可生成 范围内的均匀随机整数,试写一个方法 rand10 生成 范围内的均匀随机整数。
按照之前的经验,我们需要根据 rand7 生成一些等概率的事件,并选取 10 个等概率事件对应于 1~10 数字的生成即可。对于未被选取的事件,我们将重新执行步骤。
假设 x 是这个 区间上的一个随机整数,那么 x%10+1 就是均匀分布在 区间上的整数。由于 (rand7()-1)*7+rand7() 可以构造出均匀分布在 的随机数,我们可以将 这样的随机数剔除掉,得到的数 仍然是均匀分布在 的。
证明 (rand7()-1)*7+rand7() 可以构造出均匀分布在 的随机数
(rand7()-1)*7 得到一个离散整数集合 ,其中每个整数的出现概率也都是 。
rand7() 得到的集合 中每个整数出现的概率也是 。
显然集合 A 和 B 中任何两个元素组合可以与 之间的一个整数一一对应,也就是说 之间的任何一个数,可以唯一确定 A 和 B 中两个元素的一种组合方式,反过来也成立。
由于 A 和 B 中元素可以看成是独立事件,根据独立事件的概率公式 ,得到每个组合的概率是 。因此 (rand7()-1)*7+rand7() 生成的整数均匀分布在 之间,每个数的概率都是 。
1 | class Solution extends SolBase { |
复杂度分析:
下面是对 rand7() 函数调用次数期望的探讨。在上面的方法中,假设连续调用两次 rand7() 为一轮,那么有:
我们可以通过减少被拒绝的概率,从而减少函数调用的期望。我们可以通过合理地使用被拒绝的采样,从而对方法一进行优化:
x 在 中,我们则拒绝 x。x 被拒绝的情况下,我们得到了一个 的随机数,如果再调用一次 rand7(),那么就可以生成 的随机数。我们保留 并拒绝 。rand7(),生成 的随机数,保留 并拒绝 。假设经过上面的优化步骤为一大轮,每轮有 3 次尝试,那么有:
Java 随机数获取方法:
1 |
|
更多 Java 基础数学函数参见:站内文章【索引】Java 代码刷题热身。
给你一个单链表,随机选择链表的一个节点,并返回相应的节点值。每个节点被选中的概率一样。
在这个题目的场景中,链表是已知的,相当于在一个静态数据中随机选择链表结点。我们直接想到就是把链表中的元素都放到可以随机访问的数据结构中,如 ArrayList,再通过随机数的生成进行随机选择。
1 |
|
复杂度分析:
我们可不可以将空间复杂度降到 呢?我们可以使用蓄水池采样算法。咱们先看代码:
1 |
|
对于每次选取,我们都需要遍历所有的元素。假设最终被选中的是第 k 个元素 ,这就意味着:
因此,对于某个序号为 k 的元素,假设其被选中,则概率为:
所以列表中的每个元素,均会以 的概率被选取。
其实如果静态数据场景,要求不使用额外空间的话,可以试试随机选一个数代表链表第 n 个结点,在遍历 n 次。复杂度跟蓄水池算法差不多。
1 |
|
相关题目:
在上一小节的题目中,我们通过时间和空间的权衡,写了两种解决思路。实际上对于静态数据,每次获取随机数的时间复杂度为 的话效率实在有点低。就比如在 398. 随机数索引 - 力扣(LeetCode) 就不能使用蓄水池算法。
蓄水池采样更适合动态数据场景,如:
随机选择未知长度数据流中的某个数据
给出一个数据流,这个数据流的长度很大或者未知。并且对该数据流中数据只能访问一次。请写出一个随机选择算法,使得数据流中所有数据被选中的概率相等。
例如:
算法的过程:
一个简单的证明(当采样的数量为 1 时)已经在上一节中阐述了。下面说明当采样数量为 k 时的证明方式:
所以对于其中每个元素,被保留的概率都为 。
给你一个 下标从 0 开始 的正整数数组 w ,其中 w[i] 代表第 i 个下标的权重。
请你实现一个函数 pickIndex ,它可以 随机地 从范围 [0, w.length - 1] 内(含 0 和 w.length - 1)选出并返回一个下标。选取下标 i 的 概率 为 w[i] / sum(w) 。
对于这道题目,使用「时空权衡」小节中的方法并不能通过测试用例,要么 TLE 要么 MLE。

1 | class Solution { |
给定一个整数 n 和一个 无重复 黑名单整数数组 blacklist 。设计一种算法,从 [0, n - 1] 范围内的任意整数中选取一个 未加入 黑名单 blacklist 的整数。任何在上述范围内且不在黑名单 blacklist 中的整数都应该有 同等的可能性 被返回。
优化你的算法,使它最小化调用语言内置随机函数的次数。
通过映射,把可以进行随机的区间进行集中。
先思考一个简单的情形。假设黑名单长度为 m,且 里都是白名单中的数, 中的数都处在黑名单。那么我们的算法就是从 中随机选一个数出来就行。
在一般情况下,黑名单中的数随机分布。那么我们可以把 里处于黑名单中的数,映射到 中白名单的数就行,然后从 中随机选一个数出来作为返回结果。
1 | class Solution { |
等概率产生 0 和 1
有一个随机数发生器 f(),以概率 产生 0,概率 产生 1,请问能否利用这个随机数发生器,构造出新的发生器 g(),以 的概率产生 0 和 1 。
不能用已有的随机生成库函数。
思路:我们可以通过随机数发生器 f(),制造一些等概率事件,并定义这些事件所表示的含义。
步骤:
f(),得到以下事件的概率:
0;11 | public int g() |
等概率产生 1 到 N 之间的数
有一个随机数发生器 f(),以概率 产生 0,概率 产生 1,请问能否利用这个随机数发生器,构造出新的发生器 h(),等概率产生 1~N 。
在上一部分中,我们构造了新的发生器 g() 以 的概率产生 0 和 1 。假设 N 的二进制表示的位数为 k,那么我们可以调用 k 次 g(),每次 g() 生成的的结果组合成一个新的数。生成数的范围为 ,每个数被生成的几率相等。注意到,题目要求的 1~N 必然会在这个范围当中。如果生成的数符合输出要求则直接返回,否则重新生成。
给定方法 rand7 可生成 范围内的均匀随机整数,试写一个方法 rand10 生成 范围内的均匀随机整数。
按照之前的经验,我们需要根据 rand7 生成一些等概率的事件,并选取 10 个等概率事件对应于 1~10 数字的生成即可。对于未被选取的事件,我们将重新执行步骤。
假设 x 是这个 区间上的一个随机整数,那么 x%10+1 就是均匀分布在 区间上的整数。由于 (rand7()-1)*7+rand7() 可以构造出均匀分布在 的随机数,我们可以将 这样的随机数剔除掉,得到的数 仍然是均匀分布在 的。
证明 (rand7()-1)*7+rand7() 可以构造出均匀分布在 的随机数
(rand7()-1)*7 得到一个离散整数集合 ,其中每个整数的出现概率也都是 。
rand7() 得到的集合 中每个整数出现的概率也是 。
显然集合 A 和 B 中任何两个元素组合可以与 之间的一个整数一一对应,也就是说 之间的任何一个数,可以唯一确定 A 和 B 中两个元素的一种组合方式,反过来也成立。
由于 A 和 B 中元素可以看成是独立事件,根据独立事件的概率公式 ,得到每个组合的概率是 。因此 (rand7()-1)*7+rand7() 生成的整数均匀分布在 之间,每个数的概率都是 。
1 | class Solution extends SolBase { |
复杂度分析:
下面是对 rand7() 函数调用次数期望的探讨。在上面的方法中,假设连续调用两次 rand7() 为一轮,那么有:
我们可以通过减少被拒绝的概率,从而减少函数调用的期望。我们可以通过合理地使用被拒绝的采样,从而对方法一进行优化:
x 在 中,我们则拒绝 x。x 被拒绝的情况下,我们得到了一个 的随机数,如果再调用一次 rand7(),那么就可以生成 的随机数。我们保留 并拒绝 。rand7(),生成 的随机数,保留 并拒绝 。假设经过上面的优化步骤为一大轮,每轮有 3 次尝试,那么有: