1. 描述
-
给定一个包含 [0,n) 中不重复整数的黑名单 blacklist ,写一个函数从 [0, n) 中返回一个不在 blacklist 中的随机整数。
对它进行优化使其尽量少的调用Math.random()。 -
You are given an integer n and an array of unique integers blacklist. Design an algorithm to pick a random integer in the range [0, n - 1] that is not in blacklist. Any integer that is in the mentioned range and not in blacklist should be equally likely returned.
Optimize your algorithm such that it minimizes the call to the built-in random function of your language.
Implement the Solution class:- Solution(int n, int[] blacklist) Initializes the object with the integer n and the blacklisted integers blacklist.
- int pick() Returns a random integer in the range [0, n - 1] and not in blacklist. All the possible integers should be equally likely returned.
2. 分析
- 第一想法:每次生成随机数都判断是否在黑名单内,在则重新生成,但这样的设计不符合尽量少调用random()的要求。
- 大致思路:那么如何做到尽量少的调用random()呢?如果生成的随机数一定不在黑名单中,则每次pick()只用调用一次random(),这就需要我们把[0, n)看成一个数组,把黑名单中的数移到末尾,由于知道黑名单中有多少个数,我们就可以知道应该生成零到几的随机数了。
- 具体思路:将黑名单中的数移到末尾如何实现呢?其实数组[0, n)相当于最终被分成了定长的两块,前一部分为白名单,后一部分为黑名单。正常来说想法是遍历原数组,如果在黑名单中,则把数交换到末尾。但这样做有两个问题:一是如何快速判断当前数在不在黑名单中,二是需要确定交换的数不在黑名单中。
- 问题一的本质是如何快速查询一个数在不在一个集合中,可以通过把黑名单中的数存进哈希表来来解决。第二个问题在创建哈希表后也得到解决。同时在建立了哈希表后,就不再需要遍历数组了,只需要在哈希表中进行查找,如果哈希表中的数在数组的黑名单区间,则不用移动。
- 整体算法为:
- 把0~N分成两段,前半段为白名单,后半段为黑名单:先建立一个存有所有黑名单数的map便于查询,再将在前半段范围内的黑名单数与在后半段的白名单数建立映射(作为键值对加入map),这样相当于前半段都是白名单数
- 取随机数:只用在前半段内取随机数,若取到了黑名单数,则去map中取对应的白名单数作为返回值即可
- 把0~N分成两段,前半段为白名单,后半段为黑名单:先建立一个存有所有黑名单数的map便于查询,再将在前半段范围内的黑名单数与在后半段的白名单数建立映射(作为键值对加入map),这样相当于前半段都是白名单数
3. 代码
class Solution {
Random random;
Map<Integer, Integer> blackmap;
int thre;
public Solution(int n, int[] blacklist) {
random = new Random();
blackmap = new HashMap<>();
// 将blacklist中的数存入hashmap,便于查询一个数是否在blacklist中
for(int b: blacklist){
blackmap.put(b, 66);
}
thre = n - blacklist.length;
int last = n - 1;
for(int b: blacklist){
if(b >= thre) continue;
while(blackmap.containsKey(last)){
last--;
}
// 在hashmap中存入替换的值
blackmap.put(b, last);
// 存值后last要记得--,不然下一个黑名单中的数依然会用这个值替换
last--;
}
}
public int pick() {
int rand = random.nextInt(thre);
if(blackmap.containsKey(rand)){
return blackmap.get(rand);
} else {
return rand;
}
}
}