题目

题目来源:力扣(LeetCode)

给定一个正整数数组 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 的权重之和为 total。根据题目的要求,我们可以看成将 [1,total] 范围内的所有整数分成 n 个部分(其中 n 是数组 w 的长度),第 i 个部分恰好w[i] 个整数,并且这n个部分两两的交集为空。随 后我们[1,total] 范围内随机选择一个整数x,如果整数x被包含在第 i 个部分内,我们就返回 i。

  2. 一种较为简单的划分方法是按照从小到大的顺序依次划分每个部分。如果我们用pre[i] 表示数组 w 的前缀和:第 i 个区间的左边界就是 pre[i]−w[i]+1,右边界就是pre[i]。由于pre[i] 是单调递增的,因此 我们可以使用二分查找在 O(logn) 的时间内快速找到 i,即找出最小的满足x≤pre[i] 的下标 i。

  1. /**
  2. * @param {number[]} w
  3. */
  4. var Solution = function (w) {
  5. pre = new Array(w.length).fill(0);
  6. pre[0] = w[0];
  7. // 计数前缀和
  8. for (let i = 1; i < w.length; ++i) {
  9. pre[i] = pre[i - 1] + w[i];
  10. }
  11. this.total = _.sum(w);
  12. };
  13. /**
  14. * @return {number}
  15. */
  16. Solution.prototype.pickIndex = function () {
  17. // 生成的随机数不能包含0,否则部分用例过不了
  18. const x = Math.floor((Math.random() * this.total)) + 1;
  19. const binarySearch = (x) => {
  20. let low = 0, high = pre.length - 1;
  21. while (low < high) {
  22. // 二分
  23. const mid = Math.floor((high - low) / 2) + low;
  24. if (pre[mid] < x) {
  25. low = mid + 1;
  26. } else {
  27. high = mid;
  28. }
  29. }
  30. return low;
  31. }
  32. return binarySearch(x);
  33. };
  34. /**
  35. * Your Solution object will be instantiated and called as such:
  36. * var obj = new Solution(w)
  37. * var param_1 = obj.pickIndex()
  38. */

拓展阅读:
彩票调度:https://pages.cs.wisc.edu/~remzi/OSTEP/Chinese/09.pdf