C++和Java中的随机函数你玩明白了吗?内附LeetCode470.rand7()爆改rand10()巨详细题解,带你打败LeetCode%99选手

请添加图片描述

文章目录

  • 🚀前言
  • 🚀C++中的随机函数
    • ✈️介绍
    • ✈️使用
    • ✈️用C++的暴力求解
    • ✈️用C++的优化解法
  • 🚀Java中的Math.random()函数

🚀前言

大家好啊!阿辉在刷题时遇到一个很有意思的题LeetCode470.用rand7()实现rand10(),这道题我花了两个多小时研究🧐,好吧,别说我菜,阿辉也是收获到了一些东西,这里分享给大家!!!
题目描述:

  • 给定方法 rand7 可生成[1,7]范围内的均匀随机整数,试写一个方法rand10生成[1,10]范围内的均匀随机整数。
  • 你只能调用rand7()且不能调用其他方法。请不要使用系统的Math.random()方法。

🚀C++中的随机函数

✈️介绍

C语言中的rand()srand()这俩阿辉就不说了,相信大家都会。
阿辉在这里给大家介绍关于随机数生成的三个类,random_devicemt19937以及uniform_int_distribution,这三个类的声明都包含在<random>头文件中。

random_device:它提供了一种生成真正随机数的方法。它通常用于为伪随机数生成器提供种子值
mt19937:mt19937是C++标准库中提供的一个伪随机数生成器引擎。它基于梅森旋转算法(Mersenne Twister)实现,可以生成高质量的伪随机数序列
使用mt19937引擎可以生成均匀分布的整数和实数随机数。通常情况下,我们可以通过random_device来初始化mt19937引擎以产生更加随机的数值序列
uniform_int_distribution:用于生成均匀分布的整数随机数。uniform_int_distribution类提供了一种方法,可以在指定的整数范围内生成均匀分布的随机数。通过将该类与随机数引擎(如mt19937)结合使用,可以生成符合特定范围要求的随机整数

uniform_int_distribution设置的范围是全闭的即包括上下界,比如范围[1,7],包括1和7

✈️使用

它们如何使用呢?我们接着看:
我们来用上面介绍的三个类,来写出题目中提供的rand7()函数,均匀生成[1,7]的随机整数,上代码:

int rand7() {
	random_device rd;//设置随机数种子
	mt19937 gen(rd());//用随机数种子初始化随机数引擎
	uniform_int_distribution<int> dis(1, 7);//设置随机数范围,等概率返回[1,7]的整数包括1和7
	return dis(gen);//等概率拿到1~7的数字
}

✈️用C++的暴力求解

关于这道题的解法,怎么得到rand10()函数等概率得到[1,10]呢?这阿辉先讲一个简单且普适的方法,任何此类型题都可以套用
题目要求只能用rand7()改造出rand10()
首先我们可以用rand7()改出一个等概率返回0101发生器,怎么改,阿辉先写代码再解释:

int rand01() {//将上述rand7()改造成0,1发生器
	while(true){
		int num = rand7();//我们知道rand7()函数等概率返回1~7
		if(num != 7)//num等于7的时候让num重新生成
			return num < 4 ? 0 : 1;//1、2、3返回0;4、5、6返回1;0和1等概率返回
	}
}

解释:我们知道rand7()函数等概率返回1 ~ 7的整数,我们只取起生成的1 ~ 6的数字,对于数字7我们则重新生成,然后对于num的值只会取到1 ~ 6,然后我们只需要将1~6的数字等分成两组,然后只要取到1,2,3我们就返回0,取到4,5,6我们就返回1,这样得到10就是等概率的了

上述这种不取数字7的方式被称作拒绝取样

可能铁子们会说,咱们搞这个01发生器rand01()用什么用,阿辉告诉你这用处可就大了,有了01发生器rand01()我们可以得到任意范围的均匀随机整数
各位注意骚操作来了
比如本题的rand10()要求等概率返回[1,10],对于10,我们要表示10需要4个二进制位,所以我们只需要调401发生器即可得到rand10(),为什么?上图请添加图片描述
1~15这16个数的二进制形式都唯一的,每一位不管是0 还是1 概率都是0.5,所以得到1~15之间每一个数的概率都是1/16(总不能叫阿辉讲二项分布吧🙆‍♀️)完整代码如下:

int rand7() {
	random_device rd;//设置随机数种子
	mt19937 gen(rd());//随机数引擎
	uniform_int_distribution<int> dis(1, 7);//等概率返回[1,7]的整数包括1和7
	return dis(gen);//等概率拿到1~7的数字
}

int rand01() {//将上述rand7()改造成0,1发生器
	while(true){
		int num = rand7();//我们知道rand7()函数等概率返回1~7
		if(num != 7)//num等于7的时候让num重新生成
			return num < 4 ? 0 : 1;//1、2、3返回0;4、5、6返回1;0和1等概率返回
	}
}

int rand10() {
	while(true) {//下面每一个rand01()都表示一个二进制位,用右移操作符移到正确的位置
		int num = (rand01() << 3) + (rand01() << 2) + (rand01() << 1) + rand01();
		if(num != 0 && num <11)//得到0和11~15重新去取
		return num;
	} 
}

阿辉还写了一个验证测试,用rand10()循环生成10万次,看看各个数的出现概率是否一致

int main() {
	int TestTimes = 100000;
	int* count = new int[10]();
	for (int i = 0; i < TestTimes; i++) {
		for (int j = 1; j <= 10; j++) {
			if (rand10() == j)
				count[j - 1]++;
		}
	}
	for (int i = 1; i <= 10; i++) {
		cout << "数字" << i << "出现的概率是" << (double)count[i - 1] / (double)TestTimes << endl;
	}
	delete[] count;
	return 0;
}

在这里插入图片描述
实际测出,每一个数字出现的概率都大致是0.1
在LeetCode上面运行描述如下:
在这里插入图片描述

✈️用C++的优化解法

上面的暴力解法说实话效率不是很高,一开始阿辉测的是100万次,结果一直不出结果,我还以为代码写错了,程序死循环了,其实是效率太低了。
为什呢?01发生器rand01()1/7的概率重新调rand7()rand01()改成rand10(),在rand10()中要调用4次rand01(),每调用一次rand10()又有6/16的概率重新调4次rand01(),这样又会使rand7()重复调用,rand7()被调用的次数太多效率自然就低了

我们的优化的方向就是减少rand7()的调用,其实对于一个rand7()函数我们可以把它看做一个7进制生成器,因为rand7() - 1会等概率的得到0 ~ 6的数字,而rand7() - 1相当于权重为70的位,而(rand7() - 1) × 7k就表示权重为7k的位,这时我们只需要两个7进制位即可表示[0,48],所以调用两次rand7()函数即可等概率的返回[0,48]之间的数字(原理与上述暴力解法中二进制一样)
在这里插入图片描述
不过在[0,48]这些数字中我们只能用到[0,9]、[10,19]、[20,29]以及[30,39]这些数字,因为这些数字模上10会得到0 ~ 9,而且是等概率得到。为什么[40,48]这些数字不行,因为缺了49,加上它们会导致得到9比得到0 ~ 8的概率低,也就不是等概率了,这里我们仅有9/48的概率重复调用
代码如下:

int rand7() {
	random_device rd;//设置随机数种子
	mt19937 gen(rd());//随机数引擎
	uniform_int_distribution<int> dis(1, 7);//等概率返回[1,7]的整数包括1和7
	return dis(gen);//等概率拿到1~7的数字
}
int rand10() {
    while (true) {
        int num = (rand7() - 1) * 7 + (rand7() - 1);
        if (num >= 1 && num <= 40)
            return x % 10 + 1;
    }
}

上述代码在LeetCode运行描述如下:
在这里插入图片描述
其实还可以继续优化上面的代码我们浪费了[40,48]的数字,我们如何利用他们呢,你想只要我们得到了[40,48]的数字,说明我们还得在重复生成一次num,这一次生成的num在[40,48]的概率为仍为9/48这时又会重新生成num,我们只要让这个概率下降即可优化
如何优化:
num得到[40,48]的数字时,把num % 40,即可等概率得到0 ~ 8的数字,这时我们就得到了9进制的生成器,然后(num % 40) * 7 + rand7()就可以等概率得到[1,63]范围的数字,这些数字又可以模上10等概率得到0 ~ 9,仅有61,62,63三个数字不能用,这一次重新生成num的概率就更低了
代码如下:

int rand10() {
    while (true) {
    int x = (rand7() - 1) * 7 + (rand7() - 1); // 0~48
    if (x >= 1 && x <= 40) return x % 10 + 1;

    x = (x % 40) * 7 + rand7(); // 1~63
    if (x <= 60) return x % 10 + 1;
}

这一次直接击败LeetCode%99的人
在这里插入图片描述
至于剩下的61,62,63这三个数字还能不能优化呢?实际上是可以的,但是阿辉算过了,继续优化,下一次重复的概率仍然是1 / 21,大家下去可以尝试优化一下,阿辉在这就不展开了,方法就和优化[40,48]这9个数一样

🚀Java中的Math.random()函数

在Java中有这么一个函数Math.random()用来生成随机数,它与C++中不同,这个函数会等概率随机返回[0,1)的小数(包括0,不包括1),数学上不可能做到因为 0 ~ 1之间的小数有无穷多个,但是计算机可以,因为计算机小数有精度也就是有有限个小数

用Java写rand7()函数

 public  static int rand7(){
 //Math.random()*7即可得到[0,7)之间的全体实数不包括7,我们给它强转成int
 //就会等概率得到0~6这7个整数,然后加1就能等概率得到1~7
        return (int)(Math.random() * 7) + 1;
    }

用Java写rand7()rand10()

public class LeetCode470 {

    public static int rand7(){
        return (int)(Math.random() * 7) + 1;
    }
    public static int rand10(){
        while (true){
            int num = (rand7() - 1) * 7 + rand7()  - 1;
            if(num < 40)
                return num % 10 + 1;
            num = (num % 40) * rand7();
            if(num < 61)
                return num % 10 + 1;
        }
    }

在LeetCode的运行描述:
在这里插入图片描述


请添加图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/304317.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

应对 DevOps 中的技术债务:创新与稳定性的微妙平衡

技术性债务在DevOps到底意味着什么&#xff1f;从本质上讲&#xff0c;这是小的开发缺陷的积累&#xff0c;需要不断地返工。它可能由多种原因引起&#xff0c;例如快速交付新功能的压力&#xff0c;这可能会导致团队不得不牺牲代码的整洁和完善。但这些不完整的小代码&#xf…

5.3 WARPS AND SIMD HARDWARE

我们现在把注意力转向线程执行中可能限制性能的方面。回想一下&#xff0c;启动CUDA内核会生成一个线程网格&#xff0c;该网格被组织为两级层次结构。在顶层&#xff0c;网格由一维、二维或三维块阵列组成。在底层&#xff0c;每个块依次由一维、二维或三维线程阵列组成。在第…

从0开始python学习-45.pytest框之将所有的用例封装到一个类中,实现极限封装,并测试用例校验

目录 1. 封装一个用于校验yaml测试用例参数的方法&#xff1a;model_util.py 2. 校验方法是否正确 3. 封装一个方法&#xff0c;用于读取所有的用例&#xff1a;test_all_case.py 1. 封装一个用于校验yaml测试用例参数的方法&#xff1a;model_util.py from dataclasses imp…

Ant Design 日期选择器 a-date-picker 的使用

代码如下&#xff1a; data() {return {initializationTime: } },<a-form-item label"上映时间" :labelCol"labelCol" :wrapperCol"wrapperCol"><a-date-pickerv-model"initializationTime"format"YYYY-MM-DD HH:mm:ss&…

物理实验2023年下B卷部分题目总结

物理实验考试每个实验的题目由5个题变成8个题了QAQ 交直流电桥 1.惠斯通电桥不适于阻值较低&#xff08;1欧以下&#xff09;电阻的原因 2.立式电桥与卧式电桥的比较&#xff08;灵敏度、准确度、测量范围&#xff09; 3.交流电桥平衡法测电容的电路接线 4.铜热电阻、热敏…

Redis基本原理和基础知识

目录 一、基本原理 &#xff08;一&#xff09;非关系型数据库 &#xff08;二&#xff09;关系型数据库与非关系型数据库的区别 &#xff08;三&#xff09;Redis简介 1.什么是Redis 2.数据存储结构 3.默认端口号 4.数据类型 &#xff08;1&#xff09;五大基础类型 …

Linux文件系统与日志服务管理

目录 一.Linux文件系统 1.inode表和block &#xff08;1&#xff09;inode &#xff08;2&#xff09;block 2.查看inode号命令 3.Linux系统文件三种主要时间属性 4.磁盘空间还剩余很多但无法继续创建文件 5.inode大小 二.日志 1.日志保存位置 2.日志文件的分类 &a…

Linux ls命令

目录 一. 配置项1.1 ls -l1.2 ls -a1.3 ls -lrt1.4 ls -ld .?* 二. 案例2.1 查看指定文件夹下文件的数量2.2 查看多个文件夹下文件信息 一. 配置项 1.1 ls -l ⏹ ls 列出当前文件夹下所有文件名称(不包含隐藏文件) jmw_num_00 jmw_num_02 jmw_num_04 jmw_num_06 jmw_n…

手机怎么把卷子的答案擦掉?4款神器帮你轻松搞定

手机怎么把卷子的答案擦掉&#xff1f;随着技术的不断进步&#xff0c;手机已经成为我们日常生活中不可或缺的一部分。除了通信和娱乐功能之外&#xff0c;手机还可以在学习和考试方面发挥重要作用。比如&#xff0c;手机可以帮助学生们进行考试前的复习和准备。学生们还可以将…

Qt 三维柱状图 Q3DBar 和 三维条形图中的数据序列 QBar3DSeries

(一) 使用 Q3DBars 图形类和 QBar3DSeries 序列类可以绘制三维柱状图 窗口右侧是用 Q3DBars 和 QBar3DSeries 绘制的三维柱状图&#xff0c;这个图只有一个QBar3DSeries序列&#xff0c;数据是按行存储的&#xff0c;可以有多行。水平方向是行坐标轴和列坐标轴&#xff0c;使用…

食品饮料加工厂需要哪些污水处理设备和工艺

食品饮料加工厂是一类特殊的工业领域&#xff0c;其生产过程中产生的污水具有较高的含有机物质浓度和COD&#xff08;化学需氧量&#xff09;指标&#xff0c;因此需要根据自身的实际情况选择合适的污水处理设备和工艺。以下是食品饮料加工厂常见的污水处理设备和工艺&#xff…

C#不会循环响应的Action设计与实现

目录 一、简述二、测试代码三、测试的输出四、核心代码五、其它 一、简述 特点&#xff1a; 不光是能防止直接的死循环调用&#xff1b;还能防止间接的死循环调用&#xff1b;还支持对不同参数判定&#xff0c;不同参数的调用可以不当循环调用&#xff1b; 消息事件系统中必…

element-ui确认框

代码示例&#xff1a; <template slot-scope"scope"><el-popconfirmtitle"确定删除吗&#xff1f;"confirm"doDelete"><el-button type"danger" slot"reference" click"del(scope.row.id)">删…

转速传感器信号正弦波方波锯齿波信号输入隔离转换器200mV~50V/5V/12V/24V转0-5V/0-12V/0-24V/集电极开路输出

特点 转速传感器信号直接输入&#xff0c;方波信号输出正弦波、锯齿波信号输入&#xff0c;方波信号输出200mV峰值微弱信号的放大与整形不改变原波形频率&#xff0c;响应速度快电源、信号&#xff1a;输入/输出 3000VDC三隔离辅助电源&#xff1a;5V、12V、15V或24V直流单电源…

自定义HBase负载均衡器MyCustomBalancer实现步骤与代码解析

目录 1.HBase默认负载均衡策略 1.1 负载均衡总体流程 1.2 不能触发负载均衡的情况 1.3 负载均衡算法 2.自定义的 HBase 负载均衡器的步骤 3.MyCustomBalancer的代码细节 3.1 balanceCluster 方法的作用 3.2balanceCluster 对数据的影响 3.3监控HBase的性能指标 3.3.…

Labelimg打标工具编译版使用介绍——免安装conda等python虚拟环境,简单易用上手快,不容易报错

首先直接给出免积分的下载地址&#xff0c;开源软件&#xff0c;直接共享给csdn的各位开发者&#xff0c;求个三连不过分吧。点赞关注收藏。谢谢各位支持 资源地址如下 1 打开D:\xxxxx\labelImg\data内的predefined_classes.txt文件&#xff0c; 修改其中的类别为自己需要的…

工程部设备巡检管理的必要性!使用智能化设备巡检系统有什么好处?

随着科技的发展&#xff0c;智能化管理已逐渐成为企业提升效率、确保设备运行安全的重要手段。工程部作为企业内维护设施运行的关键部门&#xff0c;其巡检工作的重要性不言而喻。本文将探讨如何利用智能化技术优化工程部的设备巡检工作&#xff0c;以确保设备的及时有效维护。…

西电期末考点总结

一.“打擂台” 介绍 打擂台用于找到一个数组中的最值问题&#xff0c;先设置一个虚拟擂主&#xff0c;并保证他是“最弱的”&#xff0c;然后遍历数组&#xff0c;找到“更强的”数据&#xff0c;就交换擂主&#xff0c;“打”到最后的“擂主”就是最值数据 相关题目 1004.…

【UML】第16篇 活动图

目录 一、什么是活动图 二、应用场景&#xff1a; 三、绘图符号的说明&#xff1a; 四、语法&#xff1a; 五、例图 六、建模的流程 6.1 对业务流程建模时 6.2 对用例进行活动图建模时 一、什么是活动图 活动图&#xff08;Activity Diagram&#xff09;是UML中用于描…