引言
今天刷到一到特别有意思的算法题,在这里记录下;
在数据流场景中,我们希望随机取k个数,但是此时,我们无法确认数据总数N为多少,这个时候该怎么做呢?蓄水池算法就是解决这个问题的。我们针对于k = 1和 k > 1来分析这个问题;
首先说结论,后面再通过数学归纳法来证明该问题;
论点一:当k=1时,对于第n个数,只需要保证被选中的概率为1/n即可;
论点二:当k>1时,为了保证随机选择k个数,只需要保证对于每个新数,被选中的概率为k/n,并且以1/k的概率和前面被选的k个数互换;
证明
下面是以数学归纳法,证明该问题:
k = 1
- 对于第一个数,被获取到的概率为1;
- 对于第二个数,1/2
- 对于第三个数:前面两个数被留下来的概率 = 当前数不被选中 * 前面某个数被选中 =
- ....
- 所以,我们只需要,保证当前取数的时候,被选中的概率为
大致代码如下:
func randomOne(nums []int) int {
// 这里的n无法确定
cnt := 0
ans := 0
for _, num := range nums {
cnt++
if rand.Intn(cnt) == 0 {
ans = num
}
}
return ans
}
k > 1
- 前
k个数,被选中的概率为1 - 第
个数,那么前面的数被保留的概率 = 当前数不被选中 + 当前数被选中 * 前k个数中被选中 =
(假设
k=1000) - ....
- 综上,我们只需要保证,对于新数
n,被选中的概率为k / n即可。如果被选中,将其与前面k个数中随便一个进行替换;
func randomK(nums []int, k int) []int {
res := []int{}
for i := 0; i < k; i++ {
res = append(res, nums[i])
}
cnt := k
for i := k; i < len(nums); i++ {
cnt++
r := rand.Intn(cnt) // 【0, cnt-1】在这个区间取值
if r < k {
res[r] = nums[i]
}
}
return res
}