Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Index/数学.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
| [313. 超级丑数](https://leetcode-cn.com/problems/super-ugly-number/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/super-ugly-number/solution/gong-shui-san-xie-yi-ti-shuang-jie-you-x-jyow/) | 中等 | 🤩🤩🤩 |
| [342. 4的幂](https://leetcode-cn.com/problems/power-of-four/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/power-of-four/solution/gong-shui-san-xie-zhuan-hua-wei-2-de-mi-y21lq/) | 简单 | 🤩🤩🤩 |
| [446. 等差数列划分 II - 子序列](https://leetcode-cn.com/problems/arithmetic-slices-ii-subsequence/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/arithmetic-slices-ii-subsequence/solution/gong-shui-san-xie-xiang-jie-ru-he-fen-xi-ykvk/) | 困难 | 🤩🤩🤩🤩🤩 |
| [470. 用 Rand7() 实现 Rand10()](https://leetcode-cn.com/problems/implement-rand10-using-rand7/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/implement-rand10-using-rand7/solution/gong-shui-san-xie-k-jin-zhi-zhu-wei-shen-zmd4/) | 中等 | 🤩🤩🤩🤩 |
| [477. 汉明距离总和](https://leetcode-cn.com/problems/total-hamming-distance/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/total-hamming-distance/solution/gong-shui-san-xie-ying-yong-cheng-fa-yua-g21t/) | 简单 | 🤩🤩🤩 |
| [483. 最小好进制](https://leetcode-cn.com/problems/smallest-good-base/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/smallest-good-base/solution/gong-shui-san-xie-xiang-jie-ru-he-fen-xi-r94g/) | 困难 | 🤩🤩🤩🤩 |
| [523. 连续的子数组和](https://leetcode-cn.com/problems/continuous-subarray-sum/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/continuous-subarray-sum/solution/gong-shui-san-xie-tuo-zhan-wei-qiu-fang-1juse/) | 中等 | 🤩🤩🤩🤩 |
Expand Down
136 changes: 136 additions & 0 deletions LeetCode/461-470/470. 用 Rand7() 实现 Rand10()(中等).md
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
### 题目描述

这是 LeetCode 上的 **[470. 用 Rand7() 实现 Rand10()](https://leetcode-cn.com/problems/implement-rand10-using-rand7/solution/gong-shui-san-xie-k-jin-zhi-zhu-wei-shen-zmd4/)** ,难度为 **中等**

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() ?


---

### 基本分析

给定一个随机生成 $1$ ~ $7$ 的函数,要求实现等概率返回 $1$ ~ $10$ 的函数。

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

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

* 产生 $2$ 的概率为:$\frac{1}{7} * \frac{1}{7} = \frac{1}{49}$
* 产生 $4$ 的概率为:$\frac{1}{7} * \frac{1}{7} + \frac{1}{7} * \frac{1}{7} + \frac{1}{7} * \frac{1}{7} = \frac{3}{49}$

在 $[2, 14]$ 这 $13$ 个数里面,等概率的数值不足 $10$ 个。

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

---

### $k$ 进制诸位生成 + 拒绝采样

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

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

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

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

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

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

代码:

```Java
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(\infty)$
* 空间复杂度:$O(1)$

---

### 进阶

1. 降低对 `rand7` 的调用次数

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

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

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

首先在 $[0, 48]$ 中取 $[1, 40]$ 仍为等概率,其次形如 $x1$ 的数值有 $4$ 个($1$、$11$、$21$、$31$),形如 $x2$ 的数值有 $4$ 个($2$、$12$、$22$、$32$)... 因此最终结果仍为等概率。

代码:
```Java
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(\infty)$
* 空间复杂度:$O(1)$


2. 计算 `rand7` 的期望调用次数

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

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

---

### 最后

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

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

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

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