带权重的随机选择算法
- 题干:
给你一个 下标从 0 开始 的正整数数组 w ,其中 w[i] 代表第 i 个下标的权重。
请你实现一个函数 pickIndex ,它可以 随机地 从范围 [0, w.length - 1] 内(含 0 和 w.length - 1)选出并返回一个下标。选取下标 i 的 概率 为 w[i] / sum(w) 。
例如,对于 w = [1, 3],挑选下标 0 的概率为 1 / (1 + 3) = 0.25 (即,25%),而选取下标 1 的概率为 3 / (1 + 3) = 0.75(即,75%)。 - 思路:
可以这么理解:
因为原数组的每个下标处的元素除以所有元素加起来的和就是取到其下标的概率值,或者可以换句话说:每个下标的自身的值越大,那就说明取到这个下标的概率就越大,或者我们可以理解成每个数字都代表一定的区间长度,这个区间长度越长,则取到这个数字对应下标的概率就越大!
如上图:
0下标的数字1:可对应1个格子
1下标的数字3:可对应3个格子
2下标的数字2:可对应2个格子
3下标的数字1:可对应1个格子
所以我们可以玩一个游戏,在1-7范围内随机的扔石子,这个石子落到各个颜色的区间里头时(只会落在格子里),我们就取出这个颜色对应的下标即为所求.
模拟玩游戏,需要借助于前缀和,对应上图:在前缀和里随机生成1-7的数字,即可有自己对应的下标:
扔到格子1:对应原数组的0下标
扔到格子2,3,4:对应原数组的1下标
扔到格子的5,6:对应原数组的2下标
扔到格子的7:对应原数组的3下标
至此游戏结束,我们也得了自己拿到的下标
代码:
class Solution {
private int[] presum;
private Random random = new Random();
public Solution(int[] w) {
int n=w.length;
presum=new int[n+1];
presum[0]=0;
for(int i=1;i<=n;i++){
presum[i]=presum[i-1]+w[i-1];
}
}
public int pickIndex() {
int len=presum.length;
int pick=random.nextInt(presum[len-1])+1;//生成[1,presum[n-1]]的随机整数
//在前缀和数组中找出大于等于这个随机数的第一个元素,即左边界(二分查找)
int index=left_bound(presum,pick)-1;
return index;
}
// 搜索左侧边界的二分搜索
private int left_bound(int[] nums, int target) {
if (nums.length == 0) return -1;
int left = 0, right = nums.length;
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
right = mid;
} else if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid;
}
}
return left;
}
}