Given a blacklist B
containing unique integers from [0, N)
, write a function to return a uniform random integer from [0, N)
which is NOT in B
.
Optimize it such that it minimizes the call to system’s Math.random()
.
Note:
1 <= N <= 1000000000
0 <= B.length < min(100000, N)
[0, N)
does NOT include N. See interval notation.
Example 1:
Input:
["Solution","pick","pick","pick"]
[[1,[]],[],[],[]] Output: [null,0,0,0]
Example 2:
Input:
["Solution","pick","pick","pick"]
[[2,[]],[],[],[]] Output: [null,1,1,1]
Example 3:
Input:
["Solution","pick","pick","pick"]
[[3,[1]],[],[],[]] Output: [null,0,0,2]
Example 4:
Input:
["Solution","pick","pick","pick"]
[[4,[2]],[],[],[]] Output: [null,1,3,1]
Explanation of Input Syntax:
The input is two lists: the subroutines called and their arguments. Solution
's constructor has two arguments, N
and the blacklist B
. pick
has no arguments. Arguments are always wrapped with a list, even if there aren't any.
這道題讓我們生成一個N以內的隨機數,但是還給了一個黑名單,意思是黑名單里面的數字不能被選到。於是博主最先想到的方法就是用拒絕采樣Rejection Sampling來做,因為之前做過使用該方法的兩道題 Implement Rand10() Using Rand7() 和 Generate Random Point in a Circle,所以可以立馬想到。思路其實很簡單,就是隨機一個數,如果是黑名單里的,那么就重新隨機。為了提高在黑名單中查找數字的速度,我們將所有黑名單的數字放到一個HashSet中,這樣我們就擁有了常數級查找的速度,看似一切水到渠成,燃鵝被OJ強行打臉,TLE!那么換一種思路吧,既然你有黑名單,那么林北就有白名單,把所有沒被block的數字都放到一個新數組中,然后隨機生成數組坐標不就完了。燃鵝x2,又被OJ放倒了,MLE!不准用這么多內存。豈可修,真的沒別的辦法了嘛?!還好方法解答貼中給了一種使用HashMap的方法來做,博主仔細研讀了一番,發現確實秒啊!既然數字總共有N個,那么減去黑名單中數字的個數,就是最多能隨機出來的個數。比如N=5,黑名單中有兩個數{2, 4},那么我們最多只能隨機出三個,但是我們如果直接rand()%3,會得到0,1,2,我們發現有兩個問題,一是黑名單中的2可以隨機到,二是數字3沒法隨機到。那么我們想,能不能隨機到0或1則返回其本身,而當隨機到2到時候,我們返回的是3,我們需要建立這樣的映射,這就是使用HashMap的動機啦。我們首先將超過N - blacklist.size()的數字放入一個HashSet,這里就把{3, 4}放進去了,然后我們遍歷blacklist中的數字,如果在HashSet中的話,就將其刪除,這樣HashSet中就只有{3}了,這個需要建立映射的數字,而用什么數字建立,當然是用黑名單中的數字了,遍歷黑名單中的數字,如果小於N - blacklist.size()的話,說明是有可能隨機到的,我們和HashSet中的第一個數字建立映射,然后我們可以用個iterator,指向HashSet中的下一個數組,然后繼續建立映射。從而實現在pick函數中的移魂換影大法了,先隨機個數字,如果有映射,則返回映射值,否則返回原數字,參見代碼如下:
class Solution { public: Solution(int N, vector<int> blacklist) { unordered_set<int> st; len = N - blacklist.size(); for (int i = len; i < N; ++i) st.insert(i); for (int num : blacklist) st.erase(num); auto it = st.begin(); for (int num : blacklist) { if (num < len) m[num] = *it++; } } int pick() { int k = rand() % len; return m.count(k) ? m[k] : k; } private: unordered_map<int, int> m; int len; };
類似題目:
參考資料:
https://leetcode.com/problems/random-pick-with-blacklist/