如果给你一个 智能记录 k行内容的小笔记本,从一本你也不知道有多少行的 C++ Primer 中进行摘抄,你应该怎么做才能让抄写的时候能让书中的每一行都等概率的出现在小笔记本中?
答:准备好一个公平的轮盘和一个巨大的摇奖机,
轮盘上有k个数字,每个数字对应小笔记本的一行,抽中每个数字的概率相同
摇奖机装置可以从放入其中的 i 个纸条中等概率的抽出一个纸条
开始遍历 C++ Primer 中的每一行,对于前k行,直接将他们的行号写到纸条上,放入摇奖机中
并且在一个record数组中记录下这些行号
在k行之后,同样将行号写到纸条上,然后把纸条放到摇奖机中开始摇奖,如果摇出来的纸条是轮盘上的某个数字num,那么将这个新放入的纸条上的行号替换调record中记录的这个数字,
直到把 C++ Primer 中的所有行都遍历完,就能达到效果,
用数学归纳法就可以证明所有行都可以等概率的出现在小笔记本中,而且这个概率是 k / n
其中n 是 C++ Primer 中的行数
假设读到 i - 1行,每一行能够出现在小笔记本上的概率是 k / (i - 1)
那么读到 i - 1 行时,第一行能够进入小笔记本的概率是
k / (i - 1) * (1 - (k / i) * (1 / k)) = k / i
图片来自B站UP主五点七边的视频【经典算法题】蓄水池抽样算法_哔哩哔哩_bilibili
382的代码
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
ListNode* _head;
public:
Solution(ListNode* head) {
_head = head;
}
int getRandom() {
int val = _head->val;
ListNode* nextnode = _head->next;
int count = 2;
while(nextnode){
int x = rand() % count;
if(x == 0){
val = nextnode->val;
}
count++;
nextnode = nextnode->next;
}
return val;
}
};
/**
* Your Solution object will be instantiated and called as such:
* Solution* obj = new Solution(head);
* int param_1 = obj->getRandom();
*/