题目地址:
https://leetcode.com/problems/random-pick-with-weight/
给定一个长 n n n的数组 A A A, A [ i ] A[i] A[i]表示 i i i的重复次数,要求设计一个类,可以在 0 ∼ n − 1 0\sim n-1 0∼n−1这 n n n个数里,随机挑选数,使得 x x x的出现概率恰好等于 A [ i ] / ∑ i A [ i ] A[i]/\sum_i A[i] A[i]/∑iA[i]。
思路是利用前缀和,开一个新数组 p p p,其长也为 n n n,并且满足 p [ 0 ] = A [ 0 ] , p [ i ] − p [ i − 1 ] = A [ i ] p[0]=A[0], p[i]-p[i-1]=A[i] p[0]=A[0],p[i]−p[i−1]=A[i]。挑选数的过程可以这样做,先利用均匀分布随机变量产生一个 0 ∼ A [ n − 1 ] − 1 0\sim A[n-1]-1 0∼A[n−1]−1的随机数 x x x,然后找到第一个下标 k k k使得 A [ k ] > x A[k]>x A[k]>x,则这次挑选出的数即为 k k k。显然 0 0 0被选择的概率就是 p [ 0 ] p [ n − 1 ] = A [ 0 ] ∑ i A [ i ] \frac{p[0]}{p[n-1]}=\frac{A[0]}{\sum_i A[i]} p[n−1]p[0]=∑iA[i]A[0]对于别的数 c c c被选择的概率就是 p [ c ] − p [ c − 1 ] p [ n − 1 ] = A [ c ] ∑ i A [ i ] \frac{p[c]-p[c-1]}{p[n-1]}=\frac{A[c]}{\sum_i A[i]} p[n−1]p[c]−p[c−1]=∑iA[i]A[c]符合条件。而找到第一个 A [ k ] > x A[k]>x A[k]>x的 k k k,可以通过二分法得到。代码如下:
class Solution {
public:
vector<int> p;
Solution(vector<int>& w) {
srand(time(nullptr));
p.resize(w.size());
p[0] = w[0];
for (int i = 1; i < w.size(); i++)
p[i] = p[i - 1] + w[i];
}
int pickIndex() {
int idx = rand() % p.back();
int l = 0, r = p.size() - 1;
while (l < r) {
int mid = l + (r - l >> 1);
if (p[mid] > idx) r = mid;
else l = mid + 1;
}
return l;
}
};
预处理时间复杂度 O ( n ) O(n) O(n),每次询问时间 O ( log n ) O(\log n) O(logn),空间 O ( n ) O(n) O(n)。