1. 利用等概率Rand5生成等概率Rand3

Rand5生成等概率Rand3

这个题目可以扩展为:利用等概率RandM生成等概率RandN (M > N)

这里,我们首先明白一个简单的知识点:如果 randN 可以等概率生成 [0, N) 之间的数

假设 N = T的整倍数,那么我们可以直接使用 RandN() / (N / T) 来等概率生成 [0, T)之间的数

比如说,Rand21可以等概率生成[0, 21)之间的数,那么 Rand21() / 3 就可以等概率生成 [0, 7)之间的数,即 实现了 Rand7函数

  1. // 利用 Rand5 实现 Rand3
  2. int Rand3() {
  3. int tmp = Rand5();
  4. while (tmp >= ) {
  5. tmp = Rand5();
  6. }
  7. return tmp;
  8. }

证明:以输出0为例,看看概率是多少。

x的第一个有效数值是通过Rand5得到的。Rand5返回0的概率是1/5,如果这事儿发生了,我们就得到了0,否则只有当Rand5返回3或4的时候,我们才有机会再次调用它来得到新的数据。

第二次调用Rand5之后,又是有1/5的概率得到0,2/5的概率得到3或4导致循环继续执行下去,如此反复。

因此概率的计算公式为:

优化一下:我们在开始就说明了一个常识,这里,如果RandM 等概率 生成 RandN 时,当 M 很大 而 N 很小时,这时很多情况是在while里循环,可能一直循环

因此,我么把while循环的边界调整一下

  1. int RandN() {
  2. int t = N; // t是小于M的,N的最大倍数
  3. while (t + N < M) {
  4. t = t + N;
  5. }
  6. int tmp = RandM();
  7. while (tmp >= t) {
  8. tmp = RandM();
  9. }
  10. return tmp / (t / N);
  11. }

2. 利用等概率Rand3生成等概率Rand5

Rand3生成等概率Rand5

这个题目可以扩展为:利用等概率RandM生成等概率RandN (M < N)

这里,我们要首先 说明一个知识点:

我们可以利用 等概率RandM函数 生成 等概率 Rand(M * M)

比如说:M = 5

  1. step1. Rand5 可以等概率生成 , , , , (/5概率)
  2.  
  3. step2. 上一步Rand5的结果为t,则 mul = M * t 等概率生成:,,,, (/5概率)
  4.  
  5. step3. 再次调用 Rand5 等概率生成 , , , , ,设结果为 s (/5概率)
  6. step4. step2 step3的两个结果相加, 就可以等概率生成 ,,... (相乘事件,/25概率)

这样就实现了 Rand25

因为这一题中M < N, 而我们可以直接利用 RandM 来 等概率生成 Rand(M * M) ,这时 M * M 很可能就大于N了,如果不大于N,那么再次 翻倍即可

假设 M * M 大于 N,联想到第一题,相信大家很容易给出第二题的解了

  1. int RandN() {
  2. int t = N; // t是小于M×M的,N的最大倍数
  3. while (t + N < M * M) {
  4. t = t + N;
  5. }
  6. int tmp = RandM() * M + RandM(); // 等概率生成 Rand(M*M)
  7. while (tmp >= t) {
  8. tmp = RandM();
  9. }
  10. return tmp;
  11. }

3. 单次遍历,等概率随机选取1个

(未知行数文件的一行文本/未知长度单链表的一个结点)

等概率随机选取1个

问题描述:假设我们有一堆数据(可能在一个单链表里,也可能在文件里),数量未知。

要求只遍历一次这些数据,随机选取其中的一个元素,任何一个元素被选到的概率相等。

O(n)时间,O(1)辅助空间(n是数据总数,但事先不知道)

解题思路:如果元素总数为n,那么每个元素被选到的概率应该是1/n。然而n只有在遍历结束的时候才能知道,在遍历的过程中,n的值还不知道,可以利用乘法规则来逐渐凑出这个概率值

  1. void GetRandomSingleLine(FILE* fp, char* randomLine) {
  2. srand(time(NULL));
  3. int i = ;
  4. char tmp[MAX_LINE];
  5. while (fgets(tmp, sizeof(tmp), fp) != NULL) {
  6. if (rand() % i == ) { // rand() % i == 0 的概率为 1/i
  7. strcpy(randomLine, tmp);
  8. }
  9. ++i;
  10. }
  11. }

当文件行数为1时,rand() % 1肯定为0,直接输出

当文件行数为2时,rand() % 2 等于0 的概率为 1/2,此时 输出第一行 和 第二行 的概率均为 1/ 2

当文件行数为3时:

输出第一行的概率是:1 * (1 - 1/2) * (1 - 1/3) = 1/3

输出第二行的概率是:1 * (1/2) * (1 - 1/3) = 1/3

输出第三行的概率是:1 - (1/3 + 1/3) = 1/3 (注意:题目要求必须输出一行)

4. 单次遍历,等概率随机选取k个:蓄水池抽样问题

(未知行数文件的K行文本/未知长度单链表的K个结点)

等概率随机选取k个(蓄水池抽样)

解题思路:先读入第 1 ~ k 行保存,以后每次读入第 行,都以 k / i 的概率把刚读入的一行随机替换之前保存的 行中的一行。

  1. void GetRandomLines(FILE* fp, char** randomLine, int k) {
  2. srand(time(NULL));
  3.  
  4. char tmp[MAX_LINE];
  5. // 读入文件第1到k行
  6. for (int i = ; i < k; ++i) {
  7. fgets(randomLine[i], MAX_LINE, fp);
  8. }
  9.  
  10. int i = k + ;
  11. while (fgets(tmp, MAX_LINE, fp) != NULL) {
  12. if (rand() % i < k) {
  13. // 随机替换已有的某一行
  14. int j = rand() % k;
  15. strcpy(randomLine[j], tmp);
  16. }
  17. ++i;
  18. }
  19. }

5. 等概率随机排列数组(随机洗牌 std::random_shuffle)

随机洗牌

问题描述:假设有一个数组,包含n个元素。现在要重新排列这些元素,要求每个元素被放到任何一个位置的概率都相等(即1/n),

并且直接在数组上重排(in place),不要生成新的数组。用O(n) 时间、O(1)辅助空间。

解题思路:

先想想如果可以开辟另外一块长度为n的辅助空间时该怎么处理,显然只要对n个元素做n次(不放回的)随机抽取就可以了。

先从n个元素中任选一个,放入新空间的第一个位置,然后再从剩下的n-1个元素中任选一个,放入第二个位置,依此类推。

按照同样的方法,但这次不开辟新的存储空间。第一次被选中的元素就要放入这个数组的第一个位置,但这个位置原来已经有别的(也可能就是这个)元素了,这时候只要把原来的元素跟被选中的元素互换一下就可以了。很容易就避免了辅助空间。

  1. void Random(std::vector<int>& num) {
  2. srand(time(NULL));
  3. for (int i = num.size() - ; i >= ; --i) {
  4. int pos = rand() % (i + 1);
  5. std::swap(num.at(i), num.at(pos));
  6. }
  7. }

6. 利用不均匀硬币产生等概率

利用不均匀硬币产生等概率

  1. 问题描述:有一枚不均匀的硬币,已知抛出此硬币后,正面向上的概率为p < p < )。请利用这枚硬币产生出概率相等的两个事件

某一次抛出硬币,正面向上的概率是p,反面向上的概率是1 - p,当p不等于0.5时,这两个事件的概率就不一样了。

怎么能凑出等概率呢?还是要利用概率的加法和乘法法则。这里用乘法,也就是连续的独立事件。

连续抛两次硬币,正反面的出现有四种情况,概率依次为:

  1. 两次均为正面:p * p
  2. 第一次正面,第二次反面:p * ( - p)
  3. 第一次反面,第二次正面:( - p) * p
  4. 两次均为反面:( - p) * ( - p)

中间两种情况的概率是完全一样的。于是问题的解法就是连续抛两次硬币,如果两次得到的相同则重新抛两次;

否则根据第一次(或第二次)的正面反面情况,就可以得到两个概率相等的事件

扩展:

这一题实质就是 利用不均匀概率事件 生成 [0,1]之间的随机数

如何利用 不均匀概率事件 生活[0,n)之间的随机数呢?(即0,1,2... n-1这n个数)

我们每一轮都是连续抛n次硬币

每一轮中 正面出现次数为i,则反面出现次数为 n-i,相应概率为 C(n, i) * (Pi) * (1-p)(n-i)

当i=1或者n-1时,上述概率分别为 n*p*(1-p)(n-1) 和 n*p(n-1)*(1-p)

注意到上面2个概率的系数都是n,我们现在需要随机生成n个数,我们就可以将这两点联系起来

例如i=1,对应n种情况,我们将这n种情况对应编号 n个数

对于每一轮中正面出现次数不是1的时候,就重新再抛一轮

我要好offer之 概率题大总结的更多相关文章

  1. 我要好offer之 系统基础大总结

    1. APUE Unix环境高级编程 (1) Unix基础知识: 内核->系统调用->shell和库函数->应用软件 (2) 文件I/O:read函数返回值.进程的文件描述符表.文件 ...

  2. 我要好offer之 排序算法大总结

    1. 插入排序 (1) 直接插入排序 void StraightInsertionSort(std::vector<int>& num) { || num.size() == ) ...

  3. 我要好offer之 字符串相关大总结

    1. str*系列手写代码 a. 一定要注意末尾'\0'的处理,切记切记 b. 一定要对输入做有效性判断,多用断言就是了 int Strlen(const char* str) { assert(st ...

  4. 《剑指offer》刷题目录

    <剑指offer>刷题目录 面试题03. 数组中重复的数字 面试题04. 二维数组中的查找 面试题05. 替换空格 面试题06. 从尾到头打印链表 面试题07. 重建二叉树 面试题09. ...

  5. 历代诗词咏宁夏注释3----蔡升元:<题大清渠>

    题大清渠 蔡升元 为怜□□□□□,□□□□□□□. □□□□沙碛里,凿开峡口贺兰旁. 支分九堡通沟浍,鼎峙三渠并汉唐. 作吏尽如君任事,不难到处乐丰穰. 两渠中划大清渠,畚筑无劳民力纾.[1] 心画万 ...

  6. 浅谈《剑指offer》原题:不使用条件、循环语句求1+2+……+n

    转载自:浅谈<剑指offer>原题:求1+2+--+n 如侵犯您的版权,请联系:windeal12@qq.com <剑指offer>上的一道原题,求1+2+--+n,要求不能使 ...

  7. 《剑指offer》算法题第十二天

    今天是<剑指offer>算法题系列的最后一天了,但是这个系列并没有包括书上的所有题目,因为正如第一天所说,这些代码是在牛客网上写并且测试的,但是牛客网上并没有涵盖书上所有的题目. 今日题目 ...

  8. 我要好offer之 二叉树大总结

    一. 二叉树定义 二叉树具有天然的递归特性,凡是二叉树相关题,首先应该联想到递归 struct BinTreeNode { BinTreeNode* left; BinTreeNode* right; ...

  9. 剑指offer第12题打印从1到n位数以及大整数加法乘法

       字符和数字加减就是字符的ASCII码和数字直接加减. 方法一: 1)在字符串操作中给一个整形数字加(字符0)就是把它转化为字符,当然给一个字符减去(字符0)就可以把它转化为数字了:如果确实是最后 ...

随机推荐

  1. 《毛毛虫组》【Alpha】Scrum meeting 1

    第一天 日期:2019/6/14 1.1 今日完成任务情况以及遇到的问题. 今日完成任务情况: (1)根据数据库设计时的E-R图将创建的表进行检查确保功能的正确实现. (2)进行公共类的设计,设计出程 ...

  2. java基础—面向对象2

    一.JAVA类的定义

  3. 用Windows Native API枚举所有句柄及查找文件句柄对应文件名的方法

    枚举所有句柄的方法 由于windows并没有给出枚举所有句柄所用到的API,和进程所拥有的句柄相关的只有GetProcessHandleCount这个函数,然而这个函数只能获取到和进程相关的句柄数,不 ...

  4. Keras预训练模型下载后保存路径

    https://blog.csdn.net/xiaohuihui1994/article/details/83340080

  5. Centos7离线部署kubernetes 1.13集群记录

    一.说明 本篇主要参考kubernetes中文社区的一篇部署文章(CentOS 使用二进制部署 Kubernetes 1.13集群),并做了更详细的记录以备用. 二.部署环境 1.kubernetes ...

  6. Linux基础学习-使用vsftpd服务传输文件

    使用vsftpd服务传输文件 1 安装vsftpd [root@qdlinux ~]# yum install vsftpd Loaded plugins: product-id, search-di ...

  7. CSS基础:block,inline和inline-block

    css的display属性是前端开发中非常常见的属性,本文简单介绍下其中比较常用的属性值,即block.inline和inline-block. HTML组件中呈现一片空白区域的组件都可当盒模型(bo ...

  8. linux中vim永久显示行号、开启语法高亮

    vim ~/.vimrc 进入insert模式,在最后加二行 syntax on set nu! 保存收工. 设置用视图模式的缩进为4个空格 set smartindent set tabstop=4 ...

  9. 使用TensorFlow的卷积神经网络识别手写数字(3)-识别篇

    from PIL import Image import numpy as np import tensorflow as tf import time bShowAccuracy = True # ...

  10. 中国电信物联网平台入门学习笔记7:NB-IOT信号如何检测

    NB-IOT设备会因为信号的原因,数据发不出.但数据发不出的原因有很多,这么排除是NB-IOT信号的问题呢?那就需要NB-IOT信号检测装置. 网上的信号检测设备 作为一个常年蜗居在实验室的穷屌丝而言 ...