LC 398. 随机数索引 (opens new window) (opens new window)
中等
# 问题描述
给你一个可能含有 重复元素 的整数数组 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 * 104
-231 <= nums[i] <= 231 - 1
target
是nums
中的一个整数- 最多调用
pick
函数104
次
# 哈希表预处理
一般情况下,我们首先想到的是使用哈希表对数组进行预处理,在构造函数传入 时,遍历 并存储每个 对应的下标集合,即使用哈希表以 为键,下标集合 作为值进行存储。
在 操作时,通过 的复杂度取出所有 的集合下标,再随机一个下标进行返回。
实现代码:
/**
* @param {number[]} nums
*/
var Solution = function (nums) {
this.map = new Map()
const n = nums.length
for (let i = 0; i < n; i++) {
let list
if (this.map.has(nums[i])) {
list = this.map.get(nums[i])
} else {
list = []
}
list.push(i)
this.map.set(nums[i], list)
}
}
/**
* @param {number} target
* @return {number}
*/
Solution.prototype.pick = function (target) {
const list = this.map.get(target)
return list[~~(Math.random() * list.length)]
}
/**
* Your Solution object will be instantiated and called as such:
* var obj = new Solution(nums)
* var param_1 = obj.pick(target)
*/
- 时间复杂度:初始化的复杂度为 ; 操作的复杂度为
- 空间复杂度:
# 蓄水池抽样
如果数据流是不定长或者很大,我们无法使用诸如数组的容器存储所有满足条件的下标,哈希表预处理的方法就无法满足了,此时我们可以使用蓄水池抽样算法来解决该问题。
我们在每次 操作时,对数据流进行遍历,当我们第 次遇到满足 的数据时,我们都执行一次判断,确定「是否要将当前 i 作为最新答案候选」。具体的,我们随机生成 中的随机数,若生成的数为 (概率为),则将当前 作为最新的候选答案。最终数据流遍历完后,假设我们共有 个数据满足 ,每一位下标返回的概率均为。
证明该方法的正确性,假设我们最终得到的答案为 ,那么 成为答案的充要条件为 「遍历到 k 位置时, k 选为最新候选答案」 且 「遍历到大于 k 位置时, 均不更新最新候选答案」。该事件发生的概率为:
首项为 选为最新候选答案的概率,后面的每一项代表 不被更新的概率。化简得到:
进一步简化可得:
实现代码:
/**
* @param {number[]} nums
*/
var Solution = function (nums) {
this.nums = nums
}
/**
* @param {number} target
* @return {number}
*/
Solution.prototype.pick = function (target) {
let count = 0
let ans = 0
for (let i = 0; i < this.nums.length; i++) {
if (this.nums[i] === target) {
ans = ~~(Math.random() * ++count) === 0 ? i : ans
}
}
return ans
}
/**
* Your Solution object will be instantiated and called as such:
* var obj = new Solution(nums)
* var param_1 = obj.pick(target)
*/
- 时间复杂度:初始化的复杂度为 ; 操作的复杂度为
- 空间复杂度:
上次更新: 2023/01/31 19:48:05
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 , 转载请注明出处!