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 来进行前缀和的维护和查询。

如何快速找到某一个选定的值在哪个区间内呢?由于我们维护的前缀和数组是有序的,所以搜索过程可以使用二分法解决。在代码中,我们使用 lower_bound 函数找到数组中第一个大于等于选定值的元素。

  • 由于x是随机选自9个部分,落在每个部分的概率都是1/9,当某个区间越大,x落在这个区间的概率也越大,这样就达到了按照权重取数的这一目的
  • 总权重为数组的和,9
  • 从这个9个中随机选择一个部分
  • 然后使用二分法判断其属于哪个的区间

思路:

思路:前缀和数组 + 二分查找
    举例:
        给定数组是【3,1,2】——>
      前缀和数组【0,3,4,6】——>
      在【0,6)范围生成一个随机数r ——>
          如果r=012则返回index0
          如果r=3则返回index1
          如果r=45则返回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;
    }
}

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Yawn__

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值