本周选择了“随机”项下的三个标签,随机 + 拒绝采样 + 蓄水池抽样,共计8题(中等7题 + 困难1题),大致分为四类,按照下表的顺序刷会比较舒服~
以下按照类别和难度顺序记录了每题的思路,链接中有细致的注释和python3代码。请多指教~~

题目:给定一个单链表,随机选择链表的一个节点,并返回相应的节点值。保证每个节点被选的概率一样。
思路: 蓄水池抽样的套路,关键是理解:if random.randint(1,count) == count: res = cur.val
题目:给定一个可能含有重复元素的整数数组,要求随机输出给定的数字的索引。 您可以假设给定的数字一定存在于数组中。
思路:因为数组可能含有重复元素,为了等概率选取这个给定的数字,必须全部遍历一次。
题目:给定圆的半径和圆心的 x、y 坐标,写一个在圆中产生均匀随机点的函数 randPoint 。
思路:本题的标签是“拒绝采样”,意在考察拒绝采样的方法,例如,从正方形中随机取点,然后拒绝圆外的点。
不过,圆本来就有简洁的公式可以直接取得目标范围内的点啊...不用拒绝啦~
随机取得半径(radius * sqrt(random.random()))和弧度(2 * math.pi * random.random())的值,再计算出点的坐标。
题目:已有方法 rand7 可生成 1 到 7 范围内的均匀随机整数,试写一个方法 rand10 生成 1 到 10 范围内的均匀随机整数。
思路:
方法1:直接使用random多快啊...不过,这是违规的...
方法1.1 return random.randint(1, 10) # 执行用时: 256 ms/ 内存消耗: 17.2 MB
方法1.2 return random.choice(rand7() * 7 + [8, 9, 10]) # 执行用时: 300 ms/ 内存消耗: 17.2 MB
方法2:小学奥数~~ 执行用时: 368 ms/内存消耗: 17.3 MB
使用7个符号等概率映射出十个数字,需要抛开1-7就是7个数字的想法~
取两次,m取1-5,直接作为数值1-5。n取1-6,按照奇偶性决定为m加0还是加5
方法3:套路(7进制)
方法3.1 两位 执行用时: 432 ms/ 内存消耗: 17.3 MB
使用7个数字表达出更多个数字,构造两位7进制,(rand7() - 1) * 7 + rand7() - 1,可以取得0-48之间的数字。
方法3.2 三位 执行用时: 392 ms/ 内存消耗: 17.2 MB
使用7个数字表达出更多个数字,构造三位7进制,(rand7() - 1) * 49 +(rand7() - 1) * 7 + rand7() - 1,可以取得0-342之间的数字。
使用两位7进制数字,需在48个数字中拒绝9个,即9/48的拒绝比例。使用三位7进制数字,仅需在342个数字中拒绝3个,即3/342(1/114)的拒绝比例,每次取数需调用rand7()三次并计算,结果显示测试时间为392 ms,优于使用两位7进制。
官网还有进一步的优化算法,思路都是最大化的利用得到的数字,尽量减少调用rand7()以及计算的次数。
题目: 给定一个正整数数组 w ,其中 w[i] 代表下标 i 的权重(下标从 0 开始),请写一个函数 pickIndex ,它可以随机地获取下标 i,选取下标 i 的概率与 w[i] 成正比。
思路:因为选取下标i的概率与w[i]成正比,先计算出w[i]的和,即所有i的权重之和,再在这个权重和的范围内随机取值。然后根据取得的数值确定其对应的i的位置。
题目: 给定一个非重叠轴对齐矩形的列表 rects,写一个函数 pick 随机均匀地选取矩形覆盖的空间中的整数点。
思路: 与528题类似,部分细节不同,参见注释~
题目: 给定一个包含 [0,n) 中不重复整数的黑名单 blacklist ,写一个函数从 [0, n) 中返回一个不在 blacklist 中的随机整数。
思路: 共有N个数字,其中黑名单上有len(blacklist)个数字, 白名单上有N-len(blacklist)个数字. 为了可以使用random.randint(0, self.white_len - 1)随机取得white_len个白名单数字中的一个,使用映射关系处理前white_len个位置中不属于白名单中的数字. 细节参见注释~
题目:题中给出一个 n_rows 行 n_cols 列的二维矩阵,且所有值被初始化为 0。要求编写一个 flip 函数,均匀随机的将矩阵中的 0 变为 1,并返回该值的位置下标 [row_id,col_id];同样编写一个 reset 函数,将所有的值都重新置为 0。尽量最少调用随机函数 Math.random(),并且优化时间和空间复杂度。
思路:
方法1:拒绝采样/多次抽样 执行用时: 60 ms(99%)/ 内存消耗: 15.4 MB(30%)
随机选取数字作为一维数组的索引位置,使用集合记录翻转过的元素位置,如新选择的位置已经处理过,继续选数。
方法2:一次采样 执行用时: 60ms(99%)/ 内存消耗: 15.5 MB(15%)
方法同710。
比较下来,虽然方法2仅抽样一次,但为了实现仅需抽一次,增加了各种操作,时空复杂度和方法1差不多。