1. 问题模式:
给定一个正整数数组 w ,其中 w[i] 代表下标 i 的权重(下标从 0 开始),请写一个函数 pickIndex ,它可以随机地获取下标 i,选取下标 i 的概率与 w[i] 成正比。例如,对于 w = [1, 3],挑选下标 0 的概率为 1 / (1 + 3) = 0.25 (即,25%),而选取下标 1 的概率为 3 / (1 + 3) = 0.75(即,75%)。
也就是说,选取下标 i 的概率为 w[i] / sum(w) 。
示例 1:
输入:
["Solution","pickIndex"]
[[[1]],[]]
输出:
[null,0]
解释:
Solution solution = new Solution([1]);
solution.pickIndex(); // 返回 0,因为数组中只有一个元素,所以唯一的选择是返回下标 0。
示例 2:
输入:
["Solution","pickIndex","pickIndex","pickIndex","pickIndex","pickIndex"]
[[[1,3]],[],[],[],[],[]]
输出:
[null,1,1,1,1,0]
解释:
Solution solution = new Solution([1, 3]);
solution.pickIndex(); // 返回 1,返回下标 1,返回该下标概率为 3/4 。
solution.pickIndex(); // 返回 1
solution.pickIndex(); // 返回 1
solution.pickIndex(); // 返回 1
solution.pickIndex(); // 返回 0,返回下标 0,返回该下标概率为 1/4 。
由于这是一个随机问题,允许多个答案,因此下列输出都可以被认为是正确的:
[null,1,1,1,1,0]
[null,1,1,1,1,1]
[null,1,1,1,0,0]
[null,1,1,1,0,1]
[null,1,0,1,0,0]
......
诸若此类。
提示:
1 <= w.length <= 10000
1 <= w[i] <= 10 ^ 5
pickIndex 将被调用不超过 10000 次
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/random-pick-with-weight
2. 思路分析:
我们可以将每一个数字看成是一段一维中的线段,通过随机函数随机出当前的数字x并且计算出当前的数字x判断落在哪一段线段上,可以发现前缀和表示的就是落在哪一个线段,所以我们只需要找出当前的随机数x落在前缀和对应的哪一个区间,也即需要在前缀和数字s中找出第一个大于等于x的位置即可,查找第一个大于等于x的位置可以使用二分查找。
3. 代码如下:
from typing import List
import bisect
import random
class Solution:
s = None
def __init__(self, w: List[int]):
n = len(w)
s = [0] * n
s[0] = w[0]
for i in range(1, n):
s[i] += s[i - 1] + w[i]
self.s = s
def pickIndex(self) -> int:
s = self.s
# 随机出1~s[-1]的数字
x = random.randint(0, s[-1] - 1) + 1
# 使用bisect查找第一个大于等于x的位置, 区间的下标就是我们需要求解的答案
return bisect.bisect_left(s, x)