leetcode528.按权重随机选择
-
题目:给你一个 下标从 0 开始 的正整数数组 w ,其中 w[i] 代表第 i 个下标的权重。
请你实现一个函数 pickIndex ,它可以 随机地 从范围 [0, w.length - 1] 内(含 0 和 w.length - 1)选出并返回一个下标。选取下标 i 的 概率 为 w[i] / sum(w) 。
-
思路:利用二分查找和前缀和的知识;
-
声明一个前缀和数组preSum;利用前缀和给数组w的每个下标不同权重的值赋予不同的权重长度,前缀和数组preSum的第i个元素的两个相邻下标值之差则为w[i]的权重;
-
利用随机数生成范围为0-最后一个前缀和的值即(0-w[i]所有权重和),则随机生成的值则为目标值;利用二分搜索范围目标值的下标(此处可能没有与之恰好相等匹配的目标值,所以利用左侧二分搜索方法进行匹配)
class Solution { private Random random = new Random(); private int[] preSum; private int n; public Solution(int[] w) { this.n = w.length; // 前缀数组,下标0就是原数组w[0] preSum = new int[n]; // 下标0就是原数组w[0] preSum[0] = w[0]; for (int i = 1; i < n; i++) { preSum[i] = preSum[i-1] + w[i]; } } public int pickIndex() { // 保证取到[1, preSum[n-1]]的闭区间 int target = random.nextInt(preSum[n-1]) + 1; // right可以从n-1开始,也可以从n开始,对结果的正确性没有影响 int left = 0, right = n; while (left < right) { int mid = left + (right - left) / 2; // 如果找到了,直接去当前的下标 if (preSum[mid] == target) { left =mid; break; } else if (preSum[mid] > target) { // 向左找,因为当前mid的值大于target,可能是"第一个大于target"的值,所以不能丢弃mid // 如果mid的值不再需要了(最终不会取到现在的mid),那么就可以right=mid-1; right = mid; } else { // 向右找,因为当前的mid的值比target小,永远不会取到了。所以left=mid+1; left = mid + 1; } } // left代表的含义: // 当目标元素 target 不存在数组 nums 中时,搜索左侧边界的二分搜索的返回值可以做以下几种解读: // 1、返回的这个值是 nums 中大于等于 target 的最小元素索引。 // 2、返回的这个值是 target 应该插入在 nums 中的索引位置。 // 3、返回的这个值是 nums 中小于 target 的元素个数。 return left; } }