经典面试问题:用 randX() 实现 randY()

在牛牛空间上看面经的时候经常会看到这样一个问题,就是现在有 randX()可以等概率给出 1\sim X范围内的随机数,现在要根据这个来实现 randY()

1. 当 X > Y

其实很容易想到,当用大随机数生成小随机数时可以直接舍弃掉超出范围内的结果,只保留 1\sim Y 范围内的结果,显然生成结果是等概率的,为了使算法更快,我们可以只舍弃掉最后的 X \bmod Y 个数

int randY(int Y) {
    int res;
    do {
        res = randX();
    } while(res > X - X % Y);
    return (res % Y) + 1;
}

期望次数为({m:= X - X \bmod Y})

\begin{align*}\lim_{n\to \infty}S_n = \lim_{n\to\infty}(\frac{m}{X}+(\frac{m}{X})^2+\cdots +(\frac{m}{X})^n) = \frac{m}{X-m}\end{align*}

2. 当 X < Y

现在忘掉原问题,改为: 现在有一个 randX()randY() 如何等概率地生成 1\sim X\times Y 内的数呢?其实很简单,可以把它想象为一个有 XY 列的矩阵,如何将这个二维矩阵映射到一维的 1\sim X\times Y 范围内,对于位置 (i, j) (从1开始) ,只需要构造映射函数为 (i - 1)\times Y + j 即可。自然地: 可以使用 (randX() - 1) * Y + randY() 来等概率地生成 1\sim X\times Y 内的数了。

接下来,考虑 3 维矩阵映射到 1 维,即现在有 randX()randY()randZ() 要映射到 1\sim X\times Y\times Z ,可以通过 (randX - 1) * (Y * Z) + (randY - 1) * Z + randZ() 来实现

得出一般性的结论,一个 n 维矩阵,矩阵维度分别为 d_1\times d_2\times \cdots d_n ,而 (i_1,i_2,\cdots,i_n) 表示元素的 n 维坐标,则一维映射公式可以表示为(令 d_{n+1} = 1 )

\begin{align*}\mathrm{index} = \sum_{k = 1}^n (i_k - 1)\times (\prod_{j = k+1}^{n+1} d_j) + 1\end{align*}

映射函数只需要将公式里的 i_k 分别替换为对应的 Rand() 函数即可。

现在回到问题,怎么通过 randX() 来实现 randY() 呢?

很简单,我们只需要进行多次采样,假设每次采样的结果为 1\sim d_i(d_i\le X) ,然后通过 d_i 的组合

\begin{align*}d_1\times d_2\times \cdots d_n \ge Y\end{align*}

通过之前构造的映射函数,我们实现了 1\sim d_1\times d_2\times \cdots d_nrand() 方法,获取 randY() 就只需要像情况1一样把多余的部分舍弃掉就好了。至于怎么通过 randX() 来获取 randd_i 呢?还是和情况1一样。

理论上来说,我们可以随意的按照想要的方式对采样方式进行组合,只要满足 d_1\times d_2\times \cdots d_n \ge Y 就行了,但是不同的组合方式在性能上却不同,我们总是想要找出一种最优的组合方式。

其实现在的问题就转化成为了,需要找一个最大质因子不超过 X 的数 m(m\ge Y) ,然后对 m 进行质因数分解,如此就能知道需要进行多少组采样,而在具体操作中,可以将一些较小的 d_i 进行合并,以减少采样次数,比如 X = 7d_1 = 2d_2 = 3 ,就不需要进行两次采样,而是进行 d' = d_1\times d_2 = 6 一次采样。

例: 用 rand7() 构造 rand100()

100 分解质因数后为 2\times 2\times 5\times 5 ,其中 2\times 2 可以合并为 4(4<7) ,所以可以按照 d = \{4, 5, 5\} 进行 3 次采样,映射函数如上可以写为


(rand4 - 1)\times 25 + (rand5 - 1)\times 5 + (rand5 - 1) + 1

代码如下

// Big to Small, randX->randY
int randB2S(int Y) {
    int res;
    do {
        res = randX();
    } while(res > X - X % Y);
    return (res % Y) + 1;
}

int rand100() {
    int first = randB2S(4);
    int second = randB2S(5);
    int third = randB2S(5);
    return (first - 1) * 25 + (second - 1) * 5 + third;
}

最后: 关于上面的最少采样次数是很难通过一个统一的方法去找到它对应的解,只能根据具体的情况去找,所以是无法设计一个统一的 rand(int Y) 算法并且满足对 Y 最优的。

#面经##春招##暑期实习##计算机##算法#
全部评论
这里讨论的更多的是一种较优性的设计(就是一种直觉上的最优性,因为我不会最优性证明),如果不考虑性能的话,完全可以乱搞,比如例子里的7,100,搞成7*7*7=343的组合,然后再舍弃掉最后的没用的那部分就行了,非常简单。
2
送花
回复
分享
发布于 04-05 19:34 湖北
刘牛牛
点赞
送花
回复
分享
发布于 04-05 18:53 湖北
秋招专场
校招火热招聘中
官网直投
佬强的一
点赞
送花
回复
分享
发布于 04-05 19:30 陕西
佬强的一
点赞
送花
回复
分享
发布于 04-13 23:41 广东
核心思想:拒绝采样
点赞
送花
回复
分享
发布于 04-16 23:26 湖北
太强了吧,这个例子一看就懂了
点赞
送花
回复
分享
发布于 05-16 15:49 四川

相关推荐

1.自我介绍2.开放性问题(1)职业规划(2)未来期望的城市3.简历相关——MySQL(1)编程:两个表mt_order、dp_order,分别有三个字段&nbsp;brand_id(品牌id)、order_id(订单id)、price(单价),两个表使用brand_id关联,且可能出现在前一个表存在该brand_id但另一个不存在的情况,使用MySQL语言查询两表,按照brand_id&nbsp;,总销售量大于10000,的以及单笔订单均价(2)编程:表mt_order,有三个字段&nbsp;brand_id、order_id(订单id)、price(单价),使用MySQL语言查询,按照brand_id&nbsp;,总销售量大于10000,的以及单笔订单均价(3)有没有用到什么优化?索引?(4)事务的四大特性(5)隔离性是什么?(6)四种隔离方式?MySQL默认的隔离方式?4.Java——JVM(1)JVM内存分布?(2)堆中的内存分布?(3)堆中为什么这么分?(4)Younger&nbsp;CG是什么,过程是什么?5.Spring(1)Spring中的AOP是什么?例子?基于什么实现的?(2)动态代理是什么?和静态代理的区别?(3)SpringBoot涉及什么设计模式?(4)单例模式是什么?饿汉式和懒汉式?(5)工厂模式的作用是什么,举个例子?(6)忘记了6.线程(1)wait和sleep的区别?(2)线程的生命周期和状态转换?7.集合(1)ArrayList和LinkedList区别?用哪个时间复杂度更低?(2)HashMap的扩容机制?8.时间复杂度(1)递归和for循环,哪个时间复杂度大?9.项目(1)你有什么最印象深刻的项目?(2)在这个项目里你遇到过什么困难或者记忆深刻的事?(3)你这个项目是做什么的?(4)数据是哪里来的?导师给的?以什么形式给的?(5)你在里面负责什么?几个人参加的?另一个同学的分工是什么?(6)为什么对这个项目印象深刻?(7)分了几个表?所以给你的时候就是关系以及关联好了的对吧?(8)。。。10.开放题(1)你觉得上班和上学的区别是什么?(2)你的优点和缺点?(3)如果我给你一个xxx,说这周一定要做完,你会怎么办?11.反问(1)方便给我一些建议吗?(2)请问部门需要使用到什么技术栈?
查看32道真题和解析
点赞 评论 收藏
转发
10 42 评论
分享
牛客网
牛客企业服务