1.题目
给定一个可能含有重复元素的整数数组,要求随机输出给定的数字的索引。 您可以假设给定的数字一定存在于数组中。
注意:
数组大小可能非常大。 使用太多额外空间的解决方案将不会通过测试。
2.示例
int[] nums = new int[] {1,2,3,3,3}; Solution solution = new
Solution(nums);// pick(3) 应该返回索引 2,3 或者 4。每个索引的返回概率应该相等。
solution.pick(3);// pick(1) 应该返回 0。因为只有nums[0]等于1。
solution.pick(1);
3.思路及代码
不推荐(可以增加对map使用的熟练程度)
// Solution 根据题意自定义类型,用来保存键(int类型,题目所给数组中的数字)值(切片,用来放下标们)对
type Solution map[int][]int
// Constructor 遍历数组,将每个数字和对应下标以键值对的方式存入pos
func Constructor(nums []int) Solution {
pos := map[int][]int{}
for i, v := range nums {
//map的键是唯一的,面对重复的数字append只会多记录一条对应的下标
//比如{1,2,3,3,3}中3的值键值对为 3:[2 3 4]
pos[v] = append(pos[v], i)
}
return pos
}
// Pick 最后根据键(所给数字)找对应的值(保存下标们的切片)
// 再从找到的切片中返回其中的一个索引,用rand.Intn(len(indices))实现每个索引的返回概率应该相等
func (pos Solution) Pick(target int) int {
indices := pos[target]
return indices[rand.Intn(len(indices))]
}
较推荐(思路清晰且效率高)
type Solution []int
func Constructor(nums []int) Solution {
return nums
}
func (nums Solution) Pick(target int) (ans int) {
//创建了用来存放下标的切片
var find []int
for i, num := range nums {
if num == target {
find = append(find, i)
}
}
if len(find)==1 {
//题目说可以假设给定的数字一定存在于数组中,所以只有一个是0
return find[0]
}else {
//题目说可以假设给定的数字一定存在于数组中,所以len(find)>0
return find[rand.Intn(len(find))]
}
}
推荐(水塘抽样法,效率高且可以解决更特殊的问题)
type Solution []int
func Constructor(nums []int) Solution {
return nums
}
func (nums Solution) Pick(target int) (ans int) {
//没有创建切片
count := 0
for i, num := range nums {
if num == target {
//遇到目标数字的次数+1
count++
//以下就是水塘抽样需要理解的地方
//每次遇到目标数字都生成一个0到遇到次数之间的随机数
//如果是0我愿称之为命中,那么ans被暂时设为这个num的下标
//如果后面再也没有被命中的数字,那么ans就会被返回了
if rand.Intn(count) == 0 {
ans = i
}
}
}
return
}
很容易理解哪个数会被返回是随机的,但是不是如题目所说每个数返回的概率都相等呢?
官方证明如下:
学过概率论应该不难理解。
第三种和第二种思路一样,只是在输出一个随机的下标的方式上有所不同。
第三种有一点难理解却是最推荐的解法,原因其实在题目的注意里有提到:
数组大小可能非常大。 使用太多额外空间的解决方案将不会通过测试。
前两种方法都有创建切片,不过也通过了。
但是在面对"不允许创建切片"
或者"在不知道文件总行数的情况下,如何从文件中随机的抽取一行?"
这种问题时,水塘抽样就香香了。