LeetCode:382.398 水塘抽样

https://gregable.com/2007/10/reservoir-sampling.html
水塘抽样:
从一个数据流或者总量很大(且大小未知或者遍历一遍消耗较大)的情况下,随机抽取一定数量的样本。

比如要从一堆数中随机抽取1000个,怎么做?不能计算一遍大小在计算出1000个随机位置,这样就遍历两遍了。
这样做:
将前1000个数字放入一个1000大小的数组里面。
对于第1001个数字,我们产生一个0~1之间的随机数。如果这个数字小于1000/1001,则从数组中随机换掉一个数字将这个数字换上;否则,将这个数字舍弃。
计算概率:
对于这个第1001个数字,留在数组中概率为1000/1001.
对于原来数组中的数字,留在数组中的概率为1/1001 + (1000/1001)*(999/1000)=1000/1001.
所以对于前1001个数字来说的话,被选中的概率都是相同的。如果就要从1001各种选取1000个,此时返回数组就行。

再对于第1002个数字,产生随机数。如果小于1000/1002,随机换掉数组中一个数字,否则,将它丢弃。
计算概率:
对于该数字,概率为1000/1002
对于原数组中的数字,概率为1000/1001 * (2/1002 + 1000/1002 * 999/1000)=1000/1002.
所以对于它们来说,概率依然是相等的。如果就要从1002各种选取1000个,此时返回数组就行。

以此类推,总结出:
如果要从一堆数或者数据流中抽取n个数字,那么:
先将前n个数字,放进一个数组中。
对于第n+1个数字,以n/n+1的概率选中它,然后随机从数组中替换掉一个。
对于第n+i个数字,以n/n+i的概率选中它,然后随机从数组中替换掉一个。
(其实就是:对于第m个数字,有n/m的概率选上他)
重复该行为直到遍历完所有。

想一想这个行为和水塘真是差不多。一个水塘,往里面放水,首先前面放的会全部进入水塘,后来进来的水会以一定的概率替换掉水塘中的一部分水。(扯

在这里插入图片描述
然后对于382这题来说的话,这里就是n=1的情况,所以代码:

class Solution {
public:
    /** @param head The linked list's head.
        Note that the head is guaranteed to be not null, so it contains at least one node. */
    Solution(ListNode* head) {
        this->head = head;
    }
    
    /** Returns a random node's value. */
    int getRandom() {
        if (!head)
            return -1;
        int ret = head->val;
        int cnt = 2;
        auto node = head->next;
        double d = 0.0;
        while (node) {
            d = ((double)rand()) / RAND_MAX;
            if (d < (double)1.0/cnt)
                ret = node->val;
            ++cnt;
            node = node->next;
        }
        return ret;
    }
private:
    ListNode* head;
};

在这里插入图片描述
对于398这题来说,

class Solution {
public:
    Solution(vector<int>& nums):A(nums) {//对引用的初始化
    }
    
    int pick(int target) {
        int ret = -1;
        double d = 0.0;
        int cnt = 0;
        for (int i = 0; i < A.size(); ++i) {
            if (A[i] != target)
                continue;
            ++cnt;
            if (ret == -1) 
                ret = i;
            else {
                d = (double)rand() / RAND_MAX;
                if (d < (double)1.0/cnt)
                    ret = i;
            }
        }
        return ret;
    }
private:
    vector<int>& A;
};

posted @ 2019-06-06 11:01  于老师的父亲王老爷子  阅读(8)  评论(0编辑  收藏  举报