mirror of
https://github.com/ShusenTang/LeetCode.git
synced 2024-09-02 14:20:01 +00:00
3.0 KiB
3.0 KiB
382. Linked List Random Node
思路
给定一个链表,让随机返回一个节点,最直接的方法就是先统计出链表的长度,然后根据长度随机生成一个位置,然后从开头遍历到这个位置即可。 最直接的思路需要两次遍历链表,此题其实是著名的蓄水池采样(Reservoir Sampling)算法要解决的问题(398题也是用此算法),只需要一次遍历。下面介绍这个算法。
从一个群体中随机抽样出k个个体是经常会遇到的抽样问题,当事先不知道群体大小时,我可以先遍历一次数据计算出数据量N, 然后再按照上述的方法进行采样即可。 这当然可以,但是并不好,因为有时候数据只有一次访问的机会,或者很难确定数据规模有多大,例如当内存无法加载全部数据时,如何从包含未知大小的数据流中随机选取k个数据,并且要保证每个数据被抽取到的概率相等。
假设需要从数据流采样的数量的为k,蓄水池采样算法的过程如下:
- 首先构建一个可容纳k个元素的数组(形象地理解为蓄水池),先将数据流的前k个元素放入数组中;
- 然后遍历到第 k+i 个元素时,以
k/(k+i)
的概率被替换进数组中(原数组中的元素被替换的概率是相同的, 都是1/k
)。
当遍历完所有元素之后,数组中存放的k元素即为所采的样本。
证明如下:
遍历到第 k+i 个元素时,水池数组中的元素保留下来的概率 = 1 - p(第k+i个元素要替换进数组)
xp(替换掉数组中这个元素)
= 1 - k/(k+i)
x1/k
= (k+i-1)/(k+i)。
所以
- 数据流前k个元素到最后还在水池中的概率是:
k/(k+1)
x(k+1)/(k+2)
x ... x(n-1)/n
=k/n
。
数据流第 k+i 个元素要想最后在水池中,那么遍历到它时它要被替换进水池中(概率=k/(k+i)
)而且后面一直不被替换掉,所以
- 数据流第 k+i 个元素到最后在水池中的概率是:
k/(k+i)
x(k+i)/(k+i+1)
x ... x(n-1)/n
=k/n
。
综合上面两个结论可知,数据流中所有元素最后留在水池中的概率均为k/n
,得证。
此题是蓄水池抽样算法的特殊情况:k=1。有了上面的分析就不难写出代码了。
C++
class Solution {
private:
ListNode *root;
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) {
root = head;
}
/** Returns a random node's value. */
int getRandom() {
ListNode *p = root;
int res = -1;
int count = 1;
while(p){
if(rand() % count == 0) res = p -> val;
p = p -> next; count++;
}
return res;
}
};