https://leetcode.com/problems/random-pick-index/
Well.. 这一题,直白的方法太多了。譬如直接扣一个Map<Integer, List<Integer>>的cache,然后每次就先从map里面找到那个index list,然后用Random.nextInt选出来就好。这个方法构造函数是O(n)的,然后pick是O(1)的(假设random是O(1))的话。
或者pick的时候先遍历一遍nums,然后构造一个index list,在nextInt从index里面取出来。这个方法构造函数是O(1),pick是O(n)复杂度和O(n)空间复杂度。
但题目要求不能用too much extra space,所以,要不,咱就别用了。=。=....下面介绍的是一种构造函数是O(1),pick是O(n)时间O(1)空间的做法。这个做法其实之前也遇到过一些题目,不过忘了,请google或者百度蓄水池算法。。。
譬如这一个链接:https://www.cnblogs.com/snowInPluto/p/5996269.html
当然我们的蓄水池跟上面的链接不太一样,譬如说:一个数组arr有n个数,我们要随机取其中一个
那么我们可以遍历整个数组arr,第一个数字我们用100%的概率取得,第二个数字1 / 2概率替换当前的结果,第三个数字 1 / 3的概率替换当前的结果,第四个数字1 / 4的概率替换如此类推,到第n个数字用1 / n的概率替换。这样,我们就可以获得一个跟arr[random.nextInt(arr.length)]等价的效果,也就是随机取一个,并且每个数字被取到的概率是一样的。nice不nice。。。=。=
好吧,如果说一个input的数组是定长的,这样做是很蠢的。但是如果我们要从一个数据流里面,或者说不知道多长的数据里,取一个随机数字并且每一个数字被取到的概率是相等的话,这种做法就很有用了。这也就是O(1) extra space的方法解决本题的关键。在pick给定一个target的情况下,我们遍历一次数组,然后遇到target相等的数字就用到上文描述的蓄水池算法,那么我们最后就能得到我们需要的结果。那么如何做到“用 1 / n的概率替换当前的结果“这个事情呢?其实也很简单,就做一个random.nextInt(n) == 0的if判断即可,nextInt hit到任何一个数字的概率都是 1 / n,所以得到0的概率也是一样的。根据上述描述,得到代码如下:
class Solution {
int[] data;
public Solution(int[] nums) {
this.data = nums;
}
public int pick(int target) {
int counter = 0;
int candidate = 0;
Random r = new Random();
for (int i = 0; i < this.data.length; i++) {
if (this.data[i] == target) {
counter++;
if (r.nextInt(counter) == 0) {
candidate = i;
}
}
}
return candidate;
}
}