示例:
输入
["Solution", "pick", "pick", "pick"]
[[[1, 2, 3, 3, 3]], [3], [1], [3]]
输出
[null, 4, 0, 2]解释
Solution solution = new Solution([1, 2, 3, 3, 3]);
solution.pick(3); // 随机返回索引 2, 3 或者 4 之一。每个索引的返回概率应该相等。
solution.pick(1); // 返回 0 。因为只有 nums[0] 等于 1 。
solution.pick(3); // 随机返回索引 2, 3 或者 4 之一。每个索引的返回概率应该相等。
法1:使用哈希表(定长处理) pick时间复杂度为O(1) 空间复杂度为O(n)
思想:如果不考虑数组的大小,我们可以在构造函数中,用一个哈希表pos 记录 nums 中相同元素的下标。对于 pick 操作,我们可以从pos 中取出 }target 对应的下标列表,然后随机选择其中一个下标并返回。
class Solution {
Random random = new Random();
//为什么是 List<Integer> 因为将出现相同的nums[i]放在一个数组里面
Map<Integer, List<Integer>> map = new HashMap<>();
public Solution2(int[] nums) {
int n = nums.length;
for (int i = 0; i < n; i++) {
List<Integer> list = map.getOrDefault(nums[i], new ArrayList<>());
list.add(i);//记录num[i]对应的下标
map.put(nums[i], list);
}
}
public int pick(int target) {
//得到nums[i]对应的数组
List<Integer> list = map.get(target);
//从数组中随机选一个返回下标
return list.get(random.nextInt(list.size()));
}
}
法2:蓄水池算法(对于大数据的处理)不定长数据流
意思就是,从未知总体中,取若干个样本,要求每个样本被抽到的概率相等
具体做法就是:从前往后处理每个样本,每个样本被抽取的概率为 1/i ,其中i为样本的编号(从1开始),最终可以确保每个样本被抽取的概率为 1/n
如何理解:
假设最终成为答案的样本编号为 k,那么 k成为答案的充要条件为「在历到 k时被选中」并且「遍大于 k的所有元素时,均没有被选择(没有覆盖 )」
对应的概率为:
最终可得 P=1/n
因此,从前往后处理每个节点,同时记录当前节点的编号,当处理节点 k时,在 [0,k) 范围内进行随机,若随机的结果为0(发生概率为 1/k),则将节点 k的值存入答案,最后一次覆盖答案的节点即为本次抽样结果。
注:为什么随机的结果为0 概率是1/k。
理解:假设只有一个,随机数只有0,则必定选择,选择概率为1;若有两个,随机数有0,1,若是0或1都是1/2,; 若有三个,随机数有0,1,2,则0,1,2出现概率都为1/3;所以为了方便我们选择出现0,则代表1/k概率
//蓄水池算法,在N个里面随机选择一个,大数据处理(不定长数据流)
class Solution2 {
int[] a;
Random random;
public Solution2(int[] nums) {
a=nums.clone();//将nums克隆到a中
}
public int pick(int target) {
int count=0,res=0;
for(int i=0;i<a.length;i++){
if(a[i]==target) {
count++;
/*假设只有一个,随机数只有0,则必定选择,选择概率为1;
若有两个,随机数有0,1,若是0或1都是1/2,;
* 若有三个,随机数有0,1,2,则0,1,2出现概率都为1/3;
所以为了方便我们选择出现0,则代表1/k概率*/
if(random.nextInt(count)==0) res=i;
}
}
return res;
}
}
random.nextInt()函数作用:产生随机数。范围是 -2^31 ~ 2^31-1
但如果有参数,则范围是[0,n) 跟rand() 函数作用类似 rand()%100 --> [0,99)