【刷穿 LeetCode】470. 用 Rand7() 实现 Rand10() : k 进制诸位生成 + 拒绝采样

简介: 【刷穿 LeetCode】470. 用 Rand7() 实现 Rand10() : k 进制诸位生成 + 拒绝采样

网络异常,图片无法展示
|

题目描述



这是 LeetCode 上的 470. 用 Rand7() 实现 Rand10() ,难度为 中等


Tag : 「位运算」、「数学」


已有方法 rand7 可生成 1 到 7 范围内的均匀随机整数,试写一个方法 rand10 生成 1 到 10 范围内的均匀随机整数。


不要使用系统的 Math.random() 方法。


示例 1:


输入: 1
输出: [7]
复制代码


示例 2:


输入: 2
输出: [8,4]
复制代码


示例 3:


输入: 3
输出: [8,1,10]
复制代码


提示:


  1. rand7 已定义。
  2. 传入参数: n 表示 rand10 的调用次数。


进阶:


  • rand7()调用次数的 期望值 是多少 ?
  • 你能否尽量少调用 rand7() ?


基本分析



给定一个随机生成 11 ~ 77 的函数,要求实现等概率返回 11 ~ 1010 的函数。


首先需要知道,在输出域上进行定量整体偏移,仍然满足等概率,即要实现 00 ~ 66 随机器,只需要在 rand7 的返回值上进行 -11 操作即可。


但输出域的 拼接/叠加 并不满足等概率。例如 rand7() + rand7() 会产生 [2, 14][2,14] 范围内的数,但每个数并非等概率:


  • 产生 22 的概率为:\frac{1}{7} * \frac{1}{7} = \frac{1}{49}7171=491
  • 产生 44 的概率为:\frac{1}{7} * \frac{1}{7} + \frac{1}{7} * \frac{1}{7} + \frac{1}{7} * \frac{1}{7} = \frac{3}{49}7171+7171+7171=493


[2, 14][2,14]1313 个数里面,等概率的数值不足 1010 个。


因此,你应该知道「执行两次 rand7() 相加,将 [1, 10][1,10] 范围内的数进行返回,否则一直重试」的做法是错误的。


kk 进制诸位生成 + 拒绝采样



上述做法出现概率分布不均的情况,是因为两次随机值的不同组合「相加」的会出现相同的结果((1, 3)(1,3)(2, 2)(2,2)(3, 1)(3,1) 最终结果均为 44)。


结合每次执行 rand7 都可以看作一次独立事件。我们可以将两次 rand7 的结果看作生成 77 进制的两位。从而实现每个数值都唯一对应了一种随机值的组合(等概率),反之亦然。


举个🌰,设随机执行两次 rand7 得到的结果分别是 44(第一次)、77(第二次),由于我们是要 77 进制的数,因此可以先对 rand7 的执行结果进行 -11 操作,将输出域偏移到 [0, 6][0,6](仍为等概率),即得到 33(第一次)和 66(第二次),最终得到的是数值 (63)_7(63)7,数值 (63)_7(63)7 唯一对应了我们的随机值组合方案,反过来随机值组合方案也唯一对应一个 77 进制的数值。


那么根据「进制转换」的相关知识,如果我们存在一个 randK 的函数,对其执行 nn 次,我们能够等概率产生 [0, K^n - 1][0,Kn1] 范围内的数值。


回到本题,执行一次 rand7 只能产生 [0, 6][0,6] 范围内的数值,不足 1010 个;而执行 22rand7 的话则能产生 [0, 48][0,48] 范围内的数值,足够 1010 个,且等概率。


我们只需要判定生成的值是否为题意的 [1, 10][1,10] 即可,如果是的话直接返回,否则一直重试。


代码:


class Solution extends SolBase {
    public int rand10() {
        while (true) {
            int ans = (rand7() - 1) * 7 + (rand7() - 1); // 进制转换
            if (1 <= ans && ans <= 10) return ans;
        }
    }
}
复制代码


  • 时间复杂度:期望复杂度为 O(1)O(1),最坏情况下为 O(\infty)O()
  • 空间复杂度:O(1)O(1)


进阶



  1. 降低对 rand7 的调用次数


我们发现,在上述解法中,范围 [0, 48][0,48] 中,只有 [1, 10][1,10] 范围内的数据会被接受返回,其余情况均被拒绝重试。


为了尽可能少的调用 rand7 方法,我们可以从 [0, 48][0,48] 中取与 [1, 10][1,10] 成倍数关系的数,来进行转换。


我们可以取 [0, 48][0,48] 中的 [1, 40][1,40] 范围内的数来代指 [1, 10][1,10]


首先在 [0, 48][0,48] 中取 [1, 40][1,40] 仍为等概率,其次形如 x1x1 的数值有 44 个(11111121213131),形如 x2x2 的数值有 44 个(22121222223232)... 因此最终结果仍为等概率。


代码:


class Solution extends SolBase {
    public int rand10() {
        while (true) {
            int ans = (rand7() - 1) * 7 + (rand7() - 1); // 进制转换
            if (1 <= ans && ans <= 40) return ans % 10 + 1;
        }
    }
}
复制代码


  • 时间复杂度:期望复杂度为 O(1)O(1),最坏情况下为 O(\infty)O()
  • 空间复杂度:O(1)O(1)


  1. 计算 rand7 的期望调用次数


[0, 48][0,48] 中我们采纳了 [1, 40][1,40] 范围内的数值,即以调用两次为基本单位的话,有 \frac{40}{49}4940 的概率被接受返回(成功)。


成功的概率为 \frac{40}{49}4940,那么需要触发成功所需次数(期望次数)为其倒数 \frac{49}{40} = 1.2254049=1.225,每次会调用两次 rand7,因而总的期望调用次数为 1.225 * 2 = 2.451.2252=2.45


最后



这是我们「刷穿 LeetCode」系列文章的第 No.470 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。


在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。


为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:github.com/SharingSour…


在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。

相关文章
|
7月前
|
算法 Java C语言
C++和Java中的随机函数你玩明白了吗?内附LeetCode470.rand7()爆改rand10()巨详细题解,带你打败LeetCode%99选手
C++和Java中的随机函数你玩明白了吗?内附LeetCode470.rand7()爆改rand10()巨详细题解,带你打败LeetCode%99选手
|
算法
【Leetcode -405.数字转换为十六进制数 - 409.最长回文串】
【Leetcode -405.数字转换为十六进制数 - 409.最长回文串】
46 0
|
7月前
|
存储
leetcode:504. 七进制数
leetcode:504. 七进制数
37 0
【Leetcode -500.键盘行 -504.七进制数】
【Leetcode -500.键盘行 -504.七进制数】
34 0
|
机器学习/深度学习 算法 C++
每日算法系列【LeetCode 470】用 Rand7() 实现 Rand10()
每日算法系列【LeetCode 470】用 Rand7() 实现 Rand10()
102 0
LeetCode 470. 用 Rand7() 实现 Rand10()
已有方法 rand7 可生成 1 到 7 范围内的均匀随机整数,试写一个方法 rand10 生成 1 到 10 范围内的均匀随机整数。
88 0
|
程序员
【Leetcode】225. 用队列实现栈、232. 用栈实现队列
【Leetcode】225. 用队列实现栈、232. 用栈实现队列
117 0
【Leetcode】225. 用队列实现栈、232. 用栈实现队列
|
存储 前端开发 算法
一行代码解决LeetCode实现 strStr()使用JavaScript解题|前端学算法
一行代码解决LeetCode实现 strStr()使用JavaScript解题|前端学算法
171 0
一行代码解决LeetCode实现 strStr()使用JavaScript解题|前端学算法
代码随想录刷题|LeetCode 栈和队列的理论基础 232.用栈实现队列 225. 用队列实现栈
代码随想录刷题|LeetCode 栈和队列的理论基础 232.用栈实现队列 225. 用队列实现栈
代码随想录刷题|LeetCode 栈和队列的理论基础 232.用栈实现队列 225. 用队列实现栈
|
存储 算法 索引
代码随想录刷题|LeetCode KMP算法理论 28. 实现 strStr() 459.重复的子字符串
代码随想录刷题|LeetCode KMP算法理论 28. 实现 strStr() 459.重复的子字符串
代码随想录刷题|LeetCode KMP算法理论 28. 实现 strStr() 459.重复的子字符串