1. 问题

给定一个单链表,随机返回一个结点,要求每个结点被选中的概率相等。

2. 思路

在一个给定长度的数组中等概率抽取一个数,可以简单用随机函数random.randint(0, n-1)得到索引来抽取。

本题是给定了链表,当然也好做,可以事先遍历一次求长度,每次要取的时候随机求索引,然后遍历一次。

时间复杂度O(n),空间复杂度O(1)

或者事先把数据放到数组中,每次要取的时候随机求索引,然后直接取到对应的数。

时间复杂度O(1),空间复杂度O(n)

(延伸一下)如果对于长度未知,会不断增加的数据流呢?可以使用蓄水池采样(Reservoir Sampling)的方法。如果我们要从n个数(这个n会不断增加)中等概率地抽取k个数,做法如下:

(1)先取数据流的前k个数,保存在数组reservoir中。

(2)对于第i个数(k+1 <= i <= n),以k/i的概率选择是否保留第i个数,如果第i个数被选中,则从reservoir中随机选择一个数,用第i个数代替它。

(3)重复迭代第二步,reservoir中的k个数就是我们要的结果。

蓄水池采样的证明

为什么这么做可以保证等概率抽取,这里证明一下。我们现在要做的事是等概率的抽取k个数。

(1)当只有k个数的时, 每个数被抽取的概率是k/k(也就是1啦),k个数都放到蓄水池中。

(2)假设现在增加了一个数,第k+1个数,用k/(k+1)的概率选择是否保留,此时第k+1个数被保留的概率是k/(k+1)。

对于蓄水池中的k个数,它们被留下的概率是多少呢?它们原来的概率都是1,但是现在新来了一个数据,每个数据都面临被淘汰的风险。

淘汰的概率为(第k+1个数被选中的概率)乘以(每个数据被选中去淘汰的概率),即k/(k+1)*(1/k) = 1/(k+1)。

那么被留下的概率就是 1 - (被淘汰的概率) = 1- 1/(k+1) = k/(k+1)。

但是这个概率还要乘上原来被保留的概率k/k,也就是1啦,所以不用乘了,就是k/(k+1)

这样一来,所有的数据被留下的概率都是k/(k+1),满足等概率抽取,得证。

(3)推广到k+2,k+3到n和步骤二中是一样的道理。

假设现在增加了一个数,第m个数,用k/m的概率选择是否保留,此时第m个数被保留的概率是k/m。

对于蓄水池中的k个数,它们原来的被保留概率是k/(m-1),但是现在新来了一个数据,每个数据都面临被淘汰的风险。

淘汰的概率为(第m个数被选中的概率)乘以(每个数据被选中去淘汰的概率),即k/m * (1/k) = 1/m。

那么被留下来的概率就是1 - (被淘汰的概率)= 1 - 1/m = (m-1)/m。

但是这个概率还要乘上原来的被保留概率k/(m-1),即 (m-1)/m * ( k/(m-1)) = k/m。

这样一来,所有的数据被留下的概率都是k/m,满足等概率抽取,得证。

时间复杂度O(n),空间复杂度O(k),k为要取的数的个数,本题中k等于1。

3. 代码

每次random索引,然后遍历链表的做法

# Definition for singly-linked list.
# class ListNode(object):
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution(object):
def __init__(self, head):
self.head = head
p = head
i = 0
while p != None:
i += 1
p = p.next
self.n = i def getRandom(self):
i = random.randint(0,self.n-1)
p = self.head
while i:
p = p.next
i -= 1
return p.val

先用数组存起来,每次random索引后直接取得

class Solution(object):
def __init__(self, head):
self.nums = []
while head != None:
self.nums.append(head.val)
head = head.next def getRandom(self):
i = random.randint(0,len(self.nums)-1)
return self.nums[i]

蓄水池采样

import random
class ListNode(object):
def __init__(self, x):
self.val = x
self.next = None class Solution(object):
def __init__(self, head):
self.head = head def getRandom(self):
p = self.head
num = p.val
count = 2
while p.next:
p = p.next
if(random.random() < 1.0/count):
num = p.val
count += 1
return num

4. 类似题目

398. Random Pick Index

382. Linked List Random Node(蓄水池采样)的更多相关文章

  1. 【LeetCode】382. Linked List Random Node 解题报告(Python & C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 数组保存再随机选择 蓄水池抽样 日期 题目地址:ht ...

  2. [LeetCode] 382. Linked List Random Node 链表随机节点

    Given a singly linked list, return a random node's value from the linked list. Each node must have t ...

  3. Leetcode 382. Linked List Random Node

    本题可以用reservoir sampling来解决不明list长度的情况下平均概率选择元素的问题. 假设在[x_1,...,x_n]只选一个元素,要求每个元素被选中的概率都是1/n,但是n未知. 其 ...

  4. 382. Linked List Random Node

    Given a singly linked list, return a random node's value from the linked list. Each node must have t ...

  5. [LeetCode] 382. Linked List Random Node ☆☆☆

    Given a singly linked list, return a random node's value from the linked list. Each node must have t ...

  6. 382 Linked List Random Node 链表随机节点

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

  7. [LeetCode] Linked List Random Node 链表随机节点

    Given a singly linked list, return a random node's value from the linked list. Each node must have t ...

  8. LeetCode: Linked List Random Node

    这题参照http://blog.jobbole.com/42550/ 用的蓄水池算法,即更改ans的概率为1/(当前length) /** * Definition for singly-linked ...

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

    Given a singly linked list, return a random node's value from the linked list. Each node must have t ...

随机推荐

  1. break、continue、return之间的区别与联系

    今天在部署程序的时候,监控日志发现这个问题了.return的问题就这么总结哈. 在软件开发过程中,逻辑清晰是非常之重要的. 代码的规范也是非常重要的.往往细节决定成败.在编写代码的时候,一定要理解语言 ...

  2. 嵌入式Linux下Qt的中文显示

    一般情况下,嵌入式Qt界面需要中文显示,下面总结自己在项目中用到的可行的办法 1,下载一种中文简体字体,比如我用的是”方正准圆简体“,把字体文件放在ARM开发板系统的Qt字库中,即/usr/lib/f ...

  3. java基础---->多线程之Daemon(五)

    在java线程中有两种线程,一种是用户线程,另一种是守护线程.守护线程是一种特殊的线程,当进程中不存在非守护线程了,则守护线程自动销毁.今天我们通过实例来学习一下java中关于守护线程的知识.我是个平 ...

  4. fastcgi_param解释

    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;#脚本文件请求的路径 fastcgi_param QUERY_STRI ...

  5. 教主泡嫦娥[有趣的dp状态设计]

    P1342 教主泡嫦娥 时间: 1000ms / 空间: 131072KiB / Java类名: Main 背景 2012年12月21日下午3点14分35秒,全世界各国的总统以及领导人都已经汇聚在中国 ...

  6. Egret Wing4.1.0 断点调试

    一  双击代码行号左侧打断点 二 选择调试视图工具栏. 三  点击开始调试 1 wing内置播放器调试 选择此项进行调试会打开Egret内置播放器,我这里这个版本该选项无法进行断点... 2 使用本机 ...

  7. 【BZOJ3275】Number 最小割

    [BZOJ3275]Number Description 有N个正整数,需要从中选出一些数,使这些数的和最大.若两个数a,b同时满足以下条件,则a,b不能同时被选1:存在正整数C,使a*a+b*b=c ...

  8. Android中集成QQ登陆和QQ好友分享及QQ空间分享

    extends : http://blog.csdn.net/arjinmc/article/details/38439957 相关官方文档及下载地址: 如果只用分享和登陆,用lite包就可以,体积小 ...

  9. maven的install和deploy的区别

    转自:http://blog.csdn.net/u011305680/article/details/51699471 maven package:打包到本项目,一般是在项目target目录下.如果a ...

  10. org.apache.log4j日志级别

    日志记录器(Logger)是日志处理的核心组件.log4j具有7种级别(Level).日志记录器(Logger)的可用级别Level (不包括自定义级别 Level)优先级从高到低:OFF.FATAL ...