|
| 1 | +# [0710. 黑名单中的随机数](https://leetcode.cn/problems/random-pick-with-blacklist/) |
| 2 | + |
| 3 | +- 标签:数组、哈希表、数学、二分查找、排序、随机化 |
| 4 | +- 难度:困难 |
| 5 | + |
| 6 | +## 题目链接 |
| 7 | + |
| 8 | +- [0710. 黑名单中的随机数 - 力扣](https://leetcode.cn/problems/random-pick-with-blacklist/) |
| 9 | + |
| 10 | +## 题目大意 |
| 11 | + |
| 12 | +**描述**: |
| 13 | + |
| 14 | +给定一个整数 $n$ 和一个「无重复」黑名单整数数组 $blacklist$。设计一种算法,从 $[0, n - 1]$ 范围内的任意整数中选取一个「未加入」黑名单 $blacklist$ 的整数。任何在上述范围内且不在黑名单 $blacklist$ 中的整数都应该有「同等的可能性」被返回。 |
| 15 | + |
| 16 | +优化你的算法,使它最小化调用语言「内置」随机函数的次数。 |
| 17 | + |
| 18 | +**要求**: |
| 19 | + |
| 20 | +实现 Solution 类: |
| 21 | + |
| 22 | +- `Solution(int n, int[] blacklist)` 初始化整数 $n$ 和被加入黑名单 $blacklist$ 的整数。 |
| 23 | +- `int pick()` 返回一个范围为 $[0, n - 1]$ 且不在黑名单 $blacklist$ 中的随机整数。 |
| 24 | + |
| 25 | +**说明**: |
| 26 | + |
| 27 | +- $1 \le n \le 10^{9}$。 |
| 28 | +- $0 \le blacklist.length \le min(10^{5}, n - 1)$。 |
| 29 | +- $0 \le blacklist[i] \lt n$。 |
| 30 | +- $blacklist$ 中所有值都不同。 |
| 31 | +- $pick$ 最多被调用 $2 \times 10^{4}$ 次。 |
| 32 | + |
| 33 | +**示例**: |
| 34 | + |
| 35 | +- 示例 1: |
| 36 | + |
| 37 | +```python |
| 38 | +输入 |
| 39 | +["Solution", "pick", "pick", "pick", "pick", "pick", "pick", "pick"] |
| 40 | +[[7, [2, 3, 5]], [], [], [], [], [], [], []] |
| 41 | +输出 |
| 42 | +[null, 0, 4, 1, 6, 1, 0, 4] |
| 43 | + |
| 44 | +解释 |
| 45 | +Solution solution = new Solution(7, [2, 3, 5]); |
| 46 | +solution.pick(); // 返回0,任何[0,1,4,6]的整数都可以。注意,对于每一个pick的调用, |
| 47 | + // 0、1、4和6的返回概率必须相等(即概率为1/4)。 |
| 48 | +solution.pick(); // 返回 4 |
| 49 | +solution.pick(); // 返回 1 |
| 50 | +solution.pick(); // 返回 6 |
| 51 | +solution.pick(); // 返回 1 |
| 52 | +solution.pick(); // 返回 0 |
| 53 | +solution.pick(); // 返回 4 |
| 54 | +``` |
| 55 | + |
| 56 | +## 解题思路 |
| 57 | + |
| 58 | +### 思路 1:哈希表 + 随机映射 |
| 59 | + |
| 60 | +将黑名单中的数字映射到 $[n - len(blacklist), n)$ 范围内的白名单数字。 |
| 61 | + |
| 62 | +**实现步骤**: |
| 63 | + |
| 64 | +1. 计算白名单的大小:$white\_count = n - len(blacklist)$。 |
| 65 | +2. 将黑名单中 $\ge white\_count$ 的数字放入集合 $black\_set$。 |
| 66 | +3. 对于黑名单中 $< white\_count$ 的数字,将其映射到 $[white\_count, n)$ 范围内不在黑名单中的数字。 |
| 67 | +4. 随机生成 $[0, white\_count)$ 范围内的数字: |
| 68 | + - 如果在映射表中,返回映射后的值。 |
| 69 | + - 否则,直接返回该数字。 |
| 70 | + |
| 71 | +### 思路 1:代码 |
| 72 | + |
| 73 | +```python |
| 74 | +class Solution: |
| 75 | + def __init__(self, n: int, blacklist: List[int]): |
| 76 | + import random |
| 77 | + self.random = random |
| 78 | + |
| 79 | + # 白名单的大小 |
| 80 | + self.white_count = n - len(blacklist) |
| 81 | + |
| 82 | + # 黑名单中 >= white_count 的数字 |
| 83 | + black_set = set() |
| 84 | + for b in blacklist: |
| 85 | + if b >= self.white_count: |
| 86 | + black_set.add(b) |
| 87 | + |
| 88 | + # 映射表:将黑名单中 < white_count 的数字映射到 [white_count, n) 范围内的白名单数字 |
| 89 | + self.mapping = {} |
| 90 | + white = self.white_count |
| 91 | + for b in blacklist: |
| 92 | + if b < self.white_count: |
| 93 | + # 找到下一个不在黑名单中的数字 |
| 94 | + while white in black_set or white in blacklist: |
| 95 | + white += 1 |
| 96 | + self.mapping[b] = white |
| 97 | + white += 1 |
| 98 | + |
| 99 | + def pick(self) -> int: |
| 100 | + # 随机生成 [0, white_count) 范围内的数字 |
| 101 | + rand = self.random.randint(0, self.white_count - 1) |
| 102 | + # 如果在映射表中,返回映射后的值 |
| 103 | + return self.mapping.get(rand, rand) |
| 104 | + |
| 105 | + |
| 106 | +# Your Solution object will be instantiated and called as such: |
| 107 | +# obj = Solution(n, blacklist) |
| 108 | +# param_1 = obj.pick() |
| 109 | +``` |
| 110 | + |
| 111 | +### 思路 1:复杂度分析 |
| 112 | + |
| 113 | +- **时间复杂度**: |
| 114 | + - 初始化:$O(B)$,其中 $B$ 是黑名单的长度。 |
| 115 | + - 查询:$O(1)$。 |
| 116 | +- **空间复杂度**:$O(B)$,映射表的空间。 |
0 commit comments