一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第25天,点击查看活动详情。
每日刷题 2021.04.25
- leetcode原题链接:leetcode-cn.com/problems/ra…
- 难度:中等
- 方法:map不定长数组、蓄水池抽样
题目
- 给你一个可能含有 重复元素 的整数数组 nums ,请你随机输出给定的目标数字 target 的索引。你可以假设给定的数字一定存在于数组中。
- 实现 Solution 类:
- Solution(int[] nums) 用数组 nums 初始化对象。
- int pick(int target) 从 nums 中选出一个满足 nums[i] == target 的随机索引 i 。如果存在多个有效的索引,则每个索引的返回概率应当相等
示例
输入:
["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 <= nums.length <= 2 * 10^4
- -2^31 <= nums[i] <= 2^31 - 1
- target 是 nums 中的一个整数
- 最多调用 pick 函数 10^4 次
解题思路
map集合中的key存储nums数组中出现的整数,value为一个数组:存储数组中出现的整数的下标。- 注意:随机数的选取中,还需要使用
math.floor()方法。因为将value作为一个数组,那么随机数一定要是整数,才能够从数组中取到相应的结果。
蓄水池抽样
- 本题采用水塘抽样的中心思想是在不额外需要空间的情况下达到让每个索引取得
1/k的返回概率。 - 因为我们在数组的任何一点时,都无法知道数组后到底有多少个索引,我们也无法存储之前的索引都是什么,所以我们只能给出当前所知索引为最终答案的概率模拟:
- 如果只有一个索引时,我们希望第一个索引的返回几率为
100% - 如果有两个索引时,我们希望两个索引返回几率为
50% - 当遇到第三个索引时,我们希望第三个索引成为答案的几率为
33%
- 如果只有一个索引时,我们希望第一个索引的返回几率为
- 那么,第一次遇见索引时,我们希望只会返回第一个索引。第二次相见时,我们希望第二个索引代替第一个索引成为答案的概率为
50%。如此类推,我们希望第K个索引替代成为答案的概率为1/k。并且,我们惊奇的发现,之前索引成为最终答案的概率此时也为1/k(参考官方推导,概率为当前索引被选中且之后所有索引都不会被选中)
AC代码
/**
* @param {number[]} nums
*/
var Solution = function(nums) {
this.num = nums;
this.map = new Map();
this.num.forEach((value, key) => {
if(this.map.has(value)) {
let arr = this.map.get(value);
arr.push(key);
this.map.set(value, arr);
}else {
this.map.set(value, [key])
}
});
};
/**
* @param {number} target
* @return {number}
*/
Solution.prototype.pick = function(target) {
for(const m of this.map) {
if(m[0] == target){
let len = m[1].length;
let rand = Math.floor(Math.random() * len);
return m[1][rand];
}
}
};
/**
* Your Solution object will be instantiated and called as such:
* var obj = new Solution(nums)
* var param_1 = obj.pick(target)
*/
总结
- 对于
map集合中存储不定长数组的想法,最初是从建无向图的邻接表中学来,这次才能想到这样的存储结构。