LeetCode/solutions/382. Linked List Random Node.md
2019-12-19 15:58:45 +08:00

60 lines
3.0 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# [382. Linked List Random Node](https://leetcode.com/problems/linked-list-random-node/)
# 思路
给定一个链表,让随机返回一个节点,最直接的方法就是先统计出链表的长度,然后根据长度随机生成一个位置,然后从开头遍历到这个位置即可。
最直接的思路需要两次遍历链表,此题其实是著名的[蓄水池采样Reservoir Sampling](https://zh.wikipedia.org/wiki/%E6%B0%B4%E5%A1%98%E6%8A%BD%E6%A8%A3)算法要解决的问题([398题](https://leetcode.com/problems/random-pick-index/)也是用此算法),只需要一次遍历。下面介绍这个算法。
从一个群体中随机抽样出k个个体是经常会遇到的抽样问题当事先不知道群体大小时我可以先遍历一次数据计算出数据量N, 然后再按照上述的方法进行采样即可。
这当然可以但是并不好因为有时候数据只有一次访问的机会或者很难确定数据规模有多大例如当内存无法加载全部数据时如何从包含未知大小的数据流中随机选取k个数据并且要保证每个数据被抽取到的概率相等。
假设需要从数据流采样的数量的为k蓄水池采样算法的过程如下
1. 首先构建一个可容纳k个元素的数组形象地理解为蓄水池)先将数据流的前k个元素放入数组中
2. 然后遍历到第 k+i 个元素时,以 `k/(k+i)` 的概率被替换进数组中(原数组中的元素被替换的概率是相同的, 都是 `1/k`)。
当遍历完所有元素之后数组中存放的k元素即为所采的样本。
证明如下:
遍历到第 k+i 个元素时,水池数组中的元素保留下来的概率 = 1 - `p(第k+i个元素要替换进数组)`x`p(替换掉数组中这个元素)` = 1 - `k/(k+i)`x`1/k` = (k+i-1)/(k+i)。
所以
1. 数据流前k个元素到最后还在水池中的概率是: `k/(k+1)` x `(k+1)/(k+2)` x ... x `(n-1)/n` = `k/n`
数据流第 k+i 个元素要想最后在水池中,那么遍历到它时它要被替换进水池中(概率=`k/(k+i)`)而且后面一直不被替换掉,所以
2. 数据流第 k+i 个元素到最后在水池中的概率是: `k/(k+i)` x `(k+i)/(k+i+1)` x ... x `(n-1)/n` = `k/n`
综合上面两个结论可知,数据流中所有元素最后留在水池中的概率均为`k/n`,得证。
此题是蓄水池抽样算法的特殊情况k=1。有了上面的分析就不难写出代码了。
# C++
``` 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;
}
};
```