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;
};
posted @ 2022-03-30 11:43  Unparalleled_Calvin  阅读(63)  评论(0编辑  收藏  举报