持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第30天,点击查看活动详情
题目描述
给定一个整数 n 和一个 无重复 黑名单整数数组 blacklist 。设计一种算法,从 [0, n - 1] 范围内的任意整数中选取一个 未加入 黑名单 blacklist 的整数。任何在上述范围内且不在黑名单 blacklist 中的整数都应该有 同等的可能性 被返回。
优化你的算法,使它最小化调用语言 内置 随机函数的次数。
实现 Solution 类:
- Solution(int n, int[] blacklist) 初始化整数 n 和被加入黑名单 blacklist 的整数
- int pick() 返回一个范围为 [0, n - 1] 且不在黑名单 blacklist 中的随机整数
示例 1:
输入
["Solution", "pick", "pick", "pick", "pick", "pick", "pick", "pick"]
[[7, [2, 3, 5]], [], [], [], [], [], [], []]
输出
[null, 0, 4, 1, 6, 1, 0, 4]
解释
Solution solution = new Solution(7, [2, 3, 5]);
solution.pick(); // 返回0,任何[0,1,4,6]的整数都可以。注意,对于每一个pick的调用,
// 0、1、4和6的返回概率必须相等(即概率为1/4)。
solution.pick(); // 返回 4
solution.pick(); // 返回 1
solution.pick(); // 返回 6
solution.pick(); // 返回 1
solution.pick(); // 返回 0
solution.pick(); // 返回 4
提示:
- 1 <= n <= 109
- 0 <= blacklist.length <= min(105, n - 1)
- 0 <= blacklist[i] < n
- blacklist 中所有值都 不同
- pick 最多被调用 2 * 104 次
思路
最直观的方法是,在[0, n)范围内取随机数,如果在blacklist中,就重新取随机。但是因为blacklist.length的范围为10^5,确实比较大,这样可能会取到很多次在blacklist中,需要重新取随机的情况。
进一步思考,其实我们可能返回的数值个数是n-blacklist.length,所以,我们可以考虑在[0, n-blacklist.length)范围内取随机,如果取到blacklist中的值,我们要映射成在[n-blacklist.length, n)中的白名单值。由于总共范围内共有blacklist.length个黑名单值,所以在[0, n-blacklist.length)范围内黑名单的值个数跟[n-blacklist.length, n)中的白名单值个数必然是相等的。
有了这个思路,我们的做法是,创建一个Map类型的blackToWhite来保存映射关系,先遍历blacklist,把黑名单值放入blackSet中,然后再次遍历blacklist,对于小于n-blacklist.length的值,我们就要去映射一个大于等于n-blacklist.length的白名单值,我们可以定义一个index,指向n-blacklist.length,不断向后走,直到遇到一个白名单值,这样就可以把这2个值映射关系存储到blackToWhite中。继续遍历完blacklist,我们这个blackToWhite也就构建好了。
Java版本代码
class Solution {
Random r;
int left;
Map<Integer, Integer> blackToWhite;
public Solution(int n, int[] blacklist) {
r = new Random();
int len = blacklist.length;
left = n - len;
Set<Integer> blackSet = new HashSet<>();
for (int black : blacklist) {
blackSet.add(black);
}
blackToWhite = new HashMap<>();
int index = left;
for (int black : blacklist) {
if (black < left) {
while (blackSet.contains(index)) {
index++;
}
// 注意这里把index++,被映射过就不能再用了
blackToWhite.put(black, index++);
}
}
}
public int pick() {
int x = r.nextInt(left);
if (blackToWhite.containsKey(x)) {
return blackToWhite.get(x);
}
return x;
}
}