https://leetcode.com/problems/random-pick-with-weight/
https://leetcode.com/problems/random-pick-with-weight/discuss/154024/JAVA-8-lines-TreeMap
https://leetcode.com/problems/random-pick-with-weight/discuss/154044/Java-accumulated-freq-sum-and-binary-search
https://leetcode.com/problems/random-pick-with-weight/discuss/182620/Follow-up%3A-what-if-we-can-change-weights-array
Follow up: what if we can change weights array?
Given an array
w
of positive integers, where w[i]
describes the weight of index i
, write a function pickIndex
which randomly picks an index in proportion to its weight.
Note:
1 <= w.length <= 10000
1 <= w[i] <= 10^5
pickIndex
will be called at most10000
times.
Example 1:
Input: ["Solution","pickIndex"] [[[1]],[]] Output: [null,0]
Example 2:
Input: ["Solution","pickIndex","pickIndex","pickIndex","pickIndex","pickIndex"] [[[1,3]],[],[],[],[],[]] Output: [null,0,1,1,1,0]
Explanation of Input Syntax:
The input is two lists: the subroutines called and their arguments.
X. TreeMapSolution
's constructor has one argument, the array w
. pickIndex
has no arguments. Arguments are always wrapped with a list, even if there aren't anyhttps://leetcode.com/problems/random-pick-with-weight/discuss/154024/JAVA-8-lines-TreeMap
int cnt=0;
TreeMap<Integer, Integer> map= new TreeMap<>();
Random rnd= new Random();
public Solution(int[] w) {
for (int idx=0; idx<w.length; idx++){
cnt+=w[idx];
map.put(cnt, idx);
}
}
public int pickIndex() {
\\int key= map.ceilingKey(rnd.nextInt(cnt)+1); \\don't forget to +1, because rand.nextInt(cnt) generate random integer in range [0,cnt-1]
int key= map.heigherKey(rnd.nextInt(cnt));
return map.get(key);
}
X. https://leetcode.com/problems/random-pick-with-weight/discuss/154432/Very-easy-solution-based-on-uniform-sampling-with-explanation int[] arr;
int max = 0;
Random random = new Random();
public Solution(int[] w) {
int[] arr = new int[w.length];
arr[0] = w[0];
max += w[0];
for(int i=1; i<w.length; i++){
arr[i] = arr[i-1] + w[i];
max+=w[i];
}
this.arr = arr;
}
public int pickIndex() {
int rnd = random.nextInt(max) + 1; // generate random number in [1,max]
//this returns the index of the random number,
//if the number does not exist in the array it returns -(the position it should have been) - 1
int ret = Arrays.binarySearch(arr, rnd);
if(ret < 0) ret = -ret - 1; //if not in the array
return ret;
}
https://leetcode.com/problems/random-pick-with-weight/discuss/154044/Java-accumulated-freq-sum-and-binary-search
Random random;
int[] wSums;
public Solution(int[] w) {
this.random = new Random();
for(int i=1; i<w.length; ++i)
w[i] += w[i-1];
this.wSums = w;
}
public int pickIndex() {
int len = wSums.length;
int idx = random.nextInt(wSums[len-1]) + 1;
int left = 0, right = len - 1;
// search position
while(left < right){
int mid = left + (right-left)/2;
if(wSums[mid] == idx)
return mid;
else if(wSums[mid] < idx)
left = mid + 1;
else
right = mid;
}
return left;
}
X. Follow uphttps://leetcode.com/problems/random-pick-with-weight/discuss/182620/Follow-up%3A-what-if-we-can-change-weights-array
Follow up: what if we can change weights array?
Binary Indexed Tree (or Fenwick Tree) can be used if the weights can be updated many times.
Basically if we have the accumulative sum of weights for each index, pickIndex() is to find the first index which has greater accumulative sum than the random value in [0, total_weight_sum).
Basically if we have the accumulative sum of weights for each index, pickIndex() is to find the first index which has greater accumulative sum than the random value in [0, total_weight_sum).
int[] nums;
int[] bits;
Random rand;
public Solution(int[] w) {
bits = new int[w.length + 1];
nums = new int[w.length];
rand = new Random();
for (int i = 0; i < w.length; i++) {
update(i, w[i]);
}
}
public void update(int i, int val) {
int diff = val - nums[i];
nums[i] = val;
for (int j = i + 1; j < bits.length; j += j & -j) {
bits[j] += diff;
}
}
public int getSum(int i) {
int result = 0;
for (int j = i + 1; j > 0; j -= j & -j) {
result += bits[j];
}
return result;
}
public int pickIndex() {
int x = rand.nextInt(getSum(nums.length - 1));
int l = 0, r = nums.length - 1;
while (l < r) {
int m = l + (r - l) / 2;
if (getSum(m) <= x) {
l = m + 1;
} else {
r = m;
}
}
return l;
}
Customized balanced Binary Tree + HashMap should do it. Let Node be something like:
class Node{
int weight;
int sum_weights_below;
Node left, right, parent;
}
Pare it with a HashMap<Integer, Node> so that if weight[i] gets modified, corresponding i->Node gets updated too.
Updating the value of a weight[i] would also update all 'sum_weights_below' fields on a path to the root (O(logN).
Finding the Node matching a random integer between [1,sum_weights_below_root] takes O(logN). (Like a modified binary search).
Inserting new weights/Removing existing one in a balanced tree takes logN.
Updating the value of a weight[i] would also update all 'sum_weights_below' fields on a path to the root (O(logN).
Finding the Node matching a random integer between [1,sum_weights_below_root] takes O(logN). (Like a modified binary search).
Inserting new weights/Removing existing one in a balanced tree takes logN.
Not too fun to code up, especially if you have to do your own tree-balancing