Leetcode——按权重随机选择
Posted Yawn,
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Leetcode——按权重随机选择相关的知识,希望对你有一定的参考价值。
1. 按权重随机选择
(1)朴素思路
容易想到的思路就是将每个下标复制其权重份,然后随机选取一个:
如上图所示,下标 0 处权重为 2,那么我们就往一个数组中存入 2 个值为 0(对应下标值) 的节点;同理,我们存入 1 个值为 1 的节点,和 4 个值为 2 的节点
(2)前缀和优化
但是,根据题目给出的数据范围,最坏情况下要存入109个节点,说明该解法是不行的!但是,我们可以参考这个暴力的思路,来进行一定的优化。
上面思路的原理是,将数组分为 n 份,每份的权重为 w[i],如下图所示:
那么,我们只需要按照从小到大的顺序依次在数轴上划分每个部分即可,例如对于上面的 w 数组,第一份对应到数轴上的 [0, 2]区间,第二份对应到 [2, 3] 区间,第三份对应到 [3, 7]区间,然后,我们在 [0, 7] 区间随机选数就可以了。
在上述划分过程结束后,选数之后,要判断当前数字处在 w 数组哪一份内,也就是说,我们要提前维护好每一份对应的区间,而这个过程可以利用前缀和来解决
在代码中,我们维护一个数组presum 以及一个前缀和数组 sum 来进行前缀和的维护和查询。
- 由于x是随机选自9个部分,落在每个部分的概率都是1/9,当某个区间越大,x落在这个区间的概率也越大,这样就达到了按照权重取数的这一目的
- 总权重为数组的和,9
- 从这个9个中随机选择一个部分
- 然后使用二分法判断其属于哪个的区间
思路:
思路:前缀和数组 + 二分查找
举例:
给定数组是【3,1,2】——>
前缀和数组【0,3,4,6】——>
在【0,6)范围生成一个随机数r ——>
如果r=0、1、2则返回index0
如果r=3则返回index1
如果r=4、5则返回index2
前缀和数组:
preSum[0]=0;
preSum[i]=preSum[i-1]+nums[i-1];
取随机值:
int r = r.nextInt(preSum[length-1])
二分查找:
left = preSum的0
right = preSum的length-1
while(left <=right )
mid = left + (right -left)/2
如果preSum[mid] == r,则返回mid。
如果preSum[mid] > r,则想办法把mid左移,即end = mid - 1;
如果preSum[mid] < r,则先把此时mid记下来作为备选,然后想办法把mid右移,即start = mid + 1;
循环的退出条件1:找到了preSum中的恰巧正好等于r的index,这个index == w的index,所以最后返回这个index即可。
或
循环的退出条件2:找到了preSum中的、比r小的index们、中的最大的index,这个index == w的index,所以最后返回这个index即可。
代码:
class Solution {
int[] preSum;
int sum;
Random random;
public Solution(int[] w) {
preSum = new int[w.length];
preSum[0] = w[0];
sum = w[0];
// 求总和 + 前缀和
for (int i = 1; i < w.length; ++i) {
preSum[i] = preSum[i - 1] + w[i];
sum += w[i];
}
random = new Random();
}
public int pickIndex() {
// 取出一个随机数
int x = random.nextInt(sum);
// 二分查找,目的是找到x落在了那个下标的区间里
int left = 0, right = preSum.length;
while (left < right) {
int mid = (left + right) >>> 1;
if (preSum[mid] <= x) {
left = mid + 1;
} else {
right = mid;
}
}
return left;
}
}
以上是关于Leetcode——按权重随机选择的主要内容,如果未能解决你的问题,请参考以下文章
LeetCode 1480. 一维数组的动态和 / 1588. 所有奇数长度子数组的和 / 528. 按权重随机选择(随机化)