题目:Given a singly linked list, return a random node's value from the linked list. Each node must have the same probability of being chosen.

Follow up:
What if the linked list is extremely large and its length is unknown to you? Could you solve this efficiently without using extra space?

起初想到这个题目就是计算长度,然后利用rand()函数来获取一个值,取该节点返回即可。

代码可以写成如下的样子,在Leetcode上是可以AC的。

/*** Definition for singly-linked list.* struct ListNode {*     int val;*     ListNode *next;*     ListNode(int x) : val(x), next(NULL) {}* };*/
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) {listHead = head;length = 0;ListNode *p = head;while(p != NULL){p = p -> next;length++;}}/** Returns a random node's value. */int getRandom() {int randNo = rand() % length;ListNode *p = listHead;while(randNo != 0){p = p -> next;randNo--;}return p -> val;}private:ListNode * listHead;int length;
};/*** Your Solution object will be instantiated and called as such:* Solution obj = new Solution(head);* int param_1 = obj.getRandom();*/

但是,显然的,上述的代码并不满足Follow Up的条件。

让我们重新回顾一下补充条件:

What if the linked list is extremely large and its length is unknown to you? Could you solve this efficiently without using extra space?

也就是说:当链表过于庞大时,如何在不利用额外空间的情况下,令获得各节点的概率依旧是均等的呢?

我们需要将链表理解为一个数据流,就需要使用到:水塘抽样算法的内容了。

问题描述:给出一个数据流,这个数据流的长度很大或者未知。并且对该数据流中数据只能访问一次。请写出一个随机选择算法,使得数据流中所有数据被选中的概率相等。

解决思路:上面这个问题,看起来十分复杂。所以首先进行一个归纳说明,将大问题变成小问题来解决。

1) 长度为1,只有一个数据,直接返回即可,此数据被返回的概率为1.

2)长度为2,当读取第一数据时,我们发现并不是最后一个数据,我们不能直接返回,因为数据流还没结束,继续读取,到第二数据的时候,发现已经结束。所以现在的问题就是等概率返回其中的一个,显然概率为0.5。所以此时我们可以生成一个0到1的随机数p,如果p小于0.5,返回第二个,如果大于0.5,返回第一个。显然此时两个数据被返回的概率是一样的。也就是说,在选第二个数的时候,以1/2的概率决定,是否要替换掉前一个数。

3)长度为3,我们可以事先分析得到,为了满足题意,需要保证每个数据返回的概率都是1/3。接下来分析数据流,首先读取第一个数据,然后在读取第二个数据,此时可以按2)处理,保留一个数据,每个数据显然为1/2。此时读取第三个数据,发现到尾部了,为了满足题意,此时需要一1/3的概率决定是否取此数据。现在分析前两个数是否也是以1/3的概率返回,如果是则总体都满足。数据1和数据2同时留下的概率为:1/2 *(1-1/3)= 1/3。1/2只在数据1和数据2pk时,能留下的概率,1-1/3指数据3不被留下的概率。所以,对长度为3的数据流,在读取第三个数据时,我们可以生成一个0到1的随机数p,如果p小于1/3,返回第三个数据,否则,返回前面两个pk留下的数据。也就是说,在选第二个数的时候,以1/3的概率决定,是否要替换掉前两个数字pk的结果。

由上面的分析,我们可以得出结论,在取第n个数据的时候,我们生成一个0到1的随机数p,如果p小于1/n,保留第n个数。大于1/n,继续保留前面的数。直到数据流结束,返回此数。

归纳证明:下面用数学归纳法证明此结论。

1)当n=1时,第一个元素以1/1的概率返回,符合条件。

2)假设当n=k时成立,即每个元素都以1/k的概率返回,先证明n=k+1时,是否成立。

对于最后一个元素显然以1/k+1的概率返回,符合条件,对于前k个数据,被返回的概率为1/k * (1- 1/k+1)=1/k+1,满足题意。

综上所述,结论成立。

问题扩展:如果要求最后返回的结果数目是k个,那么问题就是水塘抽样问题了。

有了对上文的理解,此处可以直接替换结论:只需把上面的1/n变为k/n即可。

在取第n个数据的时候,我们生成一个0到1的随机数p,如果p小于k/n,替换池中任意一个为第n个数。大于k/n,继续保留前面的数。直到数据流结束,返回此k个数。但是为了保证计算机计算分数额准确性,一般是生成一个0到n的随机数,跟k相比,道理是一样的。


问题证明:可以以同样的方法证明。

(1)初始情况k<=n,出现在水库中的k个元素的出现概率都是一致的,都是1。
(2)第一步。第一步就是指,处理第k+1个元素的情况。

分两种情况:元素全部都没有被替换;其中某个元素被第k+1个元素替换掉。
我们先看情况2:第k+1个元素被选中的概率是k/(k+1)(根据公式k/i),所以这个新元素在水库中出现的概率就一定是k/(k+1)(不管它替换掉哪个元素,反正肯定它是以这个概率出现在水库中)。下面来看水库中剩余的元素出现的概率,也就是1-P(这个元素被替换掉的概率)。水库中任意一个元素被替换掉的概率是:(k/k+1)*(1/k)=1/(k+1),意即首先要第k+1个元素被选中,然后自己在集合的k个元素中被选中。那它出现的概率就是1-1/(k+1)=k/(k+1)。可以看出来,旧元素和新元素出现的概率是相等的。
情况1:当元素全部都没有替换掉的时候,每个元素的出现概率肯定是一样的,这很显然。但具体是多少呢?就是1-P(第k+1个元素被选中)=1-k/(k+1)=1/(k+1)。
(3)归纳法:重复上面的过程,只要证明第i步到第i+1步,所有元素出现的概率是相等的即可。

上述内容,部分参考了以下的链接:

(1)http://blog.csdn.net/javastart/article/details/50610868

(2)http://blog.csdn.net/u012102306/article/details/52014234

谢谢!

Leetcode 382. Linked List Random Node 以及 水塘抽样算法相关推荐

  1. leetcode 382. Linked List Random Node | 382. 链表随机节点(Java)

    题目 https://leetcode.com/problems/linked-list-random-node/ 题解 先存起来,再随机返回即可.不知道在考察什么? /*** Definition ...

  2. labuladong的算法小抄pdf_随机算法:水塘抽样算法

    读完本文,你可以去力扣拿下如下题目: 382.链表随机节点 398.随机数索引 -----------我最近在 LeetCode 上做到两道非常有意思的题目,382 和 398 题,关于水塘抽样算法( ...

  3. 水塘抽样算法(Reservoir Sampling Algorithm)

    文章目录 应用场景 算法步骤 算法原理 代码实现 应用场景 主要用于解决大数据流中的随机抽样问题,即:当内存有限,数据长度很大,甚至未知,那么如何从中随机选取k个数据,并且要求是等概率. 算法步骤 水 ...

  4. [Swift]LeetCode382. 链表随机节点 | Linked List Random Node

    原文地址:https://www.cnblogs.com/strengthen/p/10282841.html Given a singly linked list, return a random ...

  5. LeetCode Random Pick Index(蓄水池抽样算法)

    问题:给出一个数组,存在相同的数,随机输出目标数所在的下标 思路:使用蓄水池抽样算法,当第一次找到目标数时,作为选取.接着如果随机数等于0,则选取.在遍历完后,直接返回选取的值 具体代码参考: htt ...

  6. LeetCode Linked List Random Node(蓄水池采样算法)

    问题:给出一个单链表,随机选择链表中的一个节点,返回相应的值.保证每个节点被选的概率一样 思路:每次只保留一个数,当遇到第 i 个数时,以 1/i的概率保留它,(i-1)/i的概率保留原来的数. 具体 ...

  7. [LeetCode] 141. Linked List Cycle 单链表判圆算法

    TWO POINTER 快指针速度2 , 慢指针速度1 相对速度1,有环必然相遇 public class Solution {public boolean hasCycle(ListNode hea ...

  8. 水塘抽样(Reservoir sampling)

    水塘抽样(Reservoir sampling) 题目:给出一个数据流,这个数据流的长度很大或者未知.并且对该数据流中的数据只能访问一次.请写出一个随机选择算法,使得数据流中所有数据被选中的概率相等. ...

  9. LeetCode 382. 链表随机节点(概率)

    1. 题目 给定一个单链表,随机选择链表的一个节点,并返回相应的节点值.保证每个节点被选的概率一样. 进阶: 如果链表十分大且长度未知,如何解决这个问题?你能否使用常数级空间复杂度实现? 来源:力扣( ...

  10. Leetcode 382. 链表随机节点 解题思路及C++实现

    解题思路: 因为题目中要求需要常数级的空间复杂度,所以就需要计算链表长度了. /*** Definition for singly-linked list.* struct ListNode {* i ...

最新文章

  1. 杭州码农哀叹:新买的房子装修到一半没钱了,靠着贷款平台借钱才勉强推进。这套房子价格高达1000多万,有钱买却没钱装修!...
  2. tomcat安装及虚拟web主机
  3. linux存储--共享内存机制mmap(十二)
  4. EOS 智能合约源代码解读 (10)token合约“简介”
  5. telegraf输出MySQL_如何使用Telegraf拖尾远程日志文件
  6. linux下mysql无法看到3306端口监听
  7. python标准词匹配_用 Python 自动化办公能做到哪些有趣或有用的事情?
  8. C++中this指针的使用方法.
  9. 甘肃2019年9月计算机二级报名入口,2019年9月甘肃计算机二级考试成绩查询入口...
  10. 在MongoDB和Spring Batch中将XML转换为JSON和原始使用
  11. 升级鸿蒙系统无法选择应用,申请鸿蒙系统有一个应用选择怎么选择呢
  12. 平凡函数依赖是什么?
  13. “小米都造车了,为什么华为不造?”华为轮值董事长亲自回应
  14. BZOJ3123[Sdoi2013]森林——主席树+LCA+启发式合并
  15. python dtype o_python – 为什么dtype = str的空数据框填充“n”?
  16. 一文带你详尽剖析Miracast投屏开发和调试
  17. 青出于蓝而胜于蓝 — Vue.js对Angular.js的那些进步
  18. 服务器日志修改保存时间,日志服务保留时间
  19. wamp5 php,WAMP5:PHP环境整合安装(一)
  20. 将ipad作为Windows10系统的的扩展显示屏

热门文章

  1. 为什么很多人不喜欢甚至排斥用中文编程?
  2. 分别使用while、do-while和for循环输出1-1000中含有7或者7倍数的整数之和及个数-详解
  3. 图像处理中常用的相似度评估指标
  4. linu安装mysql5.7
  5. 8月第3周基金排行榜 | TokenInsight
  6. Windows 10 数据恢复与预防数据丢失指南
  7. 零基础学Docker【2】 | 一文带你快速学习Docker常用命令
  8. c语言表示反正弦函数,[原创]正弦和反正弦函数
  9. 梳理19年上半年图文记录笔记(php和laravel )
  10. freenas 蜗牛星际_蜗牛星际 B款 配置、安装OpenMediaVault