LeetCode 382. 链表随机节点
题目:
给定链表,要求实现随机给出链表中某元素(概率相等)
解法:
这里介绍一下现学的蓄水池算法。当我们知道总的数据规模时,我们可以用rand()%n来随机取[0, n-1]之间的数且概率相等。但是当数据规模n未知时,这种方法无法使用。假设我们要做m个元素的抽样,蓄水池的想法如下:建立一个大小为m的蓄水池,保证所有已遍历过的元素再蓄水池中出现的概率相等。
- 前\(m\)个元素遍历完之后,蓄水池被填满。
- 之后遍历到第\(k\)个元素时,以\(\frac{m}{k}\)的概率选择该元素,替换池中任意一个元素。
现在我们证明一下算法的正确性(以\(k=m+1\)为例,证明任意元素留在池子的概率为\(\frac{m}{m+1}\))
-
对原来在池子中的前\(m\)个元素任意一个,记自己被留下为事件\(B\),新元素留下为事件\(A\),则有:
\[\begin{align} P(A)&=\frac{m}{m+1}\\ P(B)&=P(B|A)*P(A)+P(B|\overline A)*P(\overline A)\\ &= \frac{m-1}{m}*\frac{m}{m+1}+1*\frac{1}{m+1}\\ &= \frac{m}{m+1} \end{align} \]此后证明略去,可知遍历\(k\)个元素,任意元素被留在池中概率为\(\frac{m}{k}\)
代码:
本题一次random操作只抽样一个,所以蓄水池大小为1。
#include <ctime>
#include <cstdlib>
class Solution {
public:
Solution(ListNode* head) {
this->head = head;
srand(time(0));
}
int getRandom() {
ListNode *work = head;
int pool = work->val;
int k = 0;
while (work->next) {
k++;
work = work->next;
if (rand()%(k+1) == 0) { // 1/k的概率选中该元素(用下标的原因,实际上有k+1个元素)
pool = work->val; // 因为池子只有一个元素,替换概率为100%
}
}
return pool;
}
private:
ListNode *head;
};