382. Linked List Random Node(蓄水池采样)
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. 类似题目
382. Linked List Random Node(蓄水池采样)的更多相关文章
- 【LeetCode】382. Linked List Random Node 解题报告(Python & C++)
作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 数组保存再随机选择 蓄水池抽样 日期 题目地址:ht ...
- [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 ...
- Leetcode 382. Linked List Random Node
本题可以用reservoir sampling来解决不明list长度的情况下平均概率选择元素的问题. 假设在[x_1,...,x_n]只选一个元素,要求每个元素被选中的概率都是1/n,但是n未知. 其 ...
- 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 ...
- [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 ...
- 382 Linked List Random Node 链表随机节点
给定一个单链表,随机选择链表的一个节点,并返回相应的节点值.保证每个节点被选的概率一样.进阶:如果链表十分大且长度未知,如何解决这个问题?你能否使用常数级空间复杂度实现?示例:// 初始化一个单链表 ...
- [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 ...
- LeetCode: Linked List Random Node
这题参照http://blog.jobbole.com/42550/ 用的蓄水池算法,即更改ans的概率为1/(当前length) /** * Definition for singly-linked ...
- [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 ...
随机推荐
- Yarn中几个专用名称
1. ResourceManager(RM) RM是一个全局的资源管理器,负责整个系统的资源管理和分配.它主要由两个组件构成:调度器(Scheduler)和应用程序管理器(Appli ...
- Linux环境下$开头的相关变量的含义
$0 这个程式的执行名字$n 这个程式的第n个参数值,n=1..9$* 这个程式的所有参数,此选项参数可超过9个.$# 这个程式的参数个数$$ 这个程式的PID(脚本运行的当前进程ID号)$! 执行上 ...
- ajax的历史
ajax (AJAX开发) 编辑 AJAX即“Asynchronous Javascript And XML”(异步JavaScript和XML),是指一种创建交互式网页应用的网页开发技术. AJAX ...
- poj_1084 剪枝-IDA*
题目大意 给出一个由2*S*(S+1)构成的S*S大小的火柴格.火柴可以构成1x1,2x2...SxS大小的方格.其中已经拿走了几个火柴,问最少再拿走几个火柴可以使得这些火柴无法构成任何一个方格. 题 ...
- java基础---->java调用oracle存储过程
存储过程是在大型数据库系统中,一组为了完成特定功能的SQL 语句集,存储在数据库中,经过第一次编译后再次调用不需要再次编译,用户通过指定存储过程的名字并给出参数(如果该存储过程带有参数)来执行它.今天 ...
- sencha touch 入门系列 (四)sencha touch 新建项目目录结构解析
通过上一章节的操作,我们的项目已经创建完成了, 大家通过http://127.0.0.1/MyFirst/应该都已经访问到了自己的应用, 接下来,我们展开我们项目,如图所示: 一.目录结构 1. .s ...
- SNMP信息泄露漏洞
SNMP协议简介 名称:SNMP(Simple Network Management Protocol)简单网络管理协议 端口:161 协议:UDP 用途:SNMP代理者以变量呈现管理资料.管理系统透 ...
- 使用servlet3.0提供的API来进行文件的上传操作
servlet 3.0针对文件上传做了一些优化,提供了一些更加人性化的API可以直接在request中的到文件的名称.文件size,MIME类型,以及用InputStream表示的文件流的信息 @Re ...
- 二、微信小游戏开发 多线程Worker
微信多线程Worker教程 微信多线程Worker API 一.创建Worker,并和当前线程通讯 多线程worker只能创建1个.能和当前线程互传数据. 创建worker 在微信开发者工具中,在当前 ...
- [算法] N 皇后
N皇后问题是一个经典的问题,在一个N*N的棋盘上放置N个皇后,每行一个并使其不能互相攻击(同一行.同一列.同一斜线上的皇后都会自动攻击). 一. 求解N皇后问题是算法中回溯法应用的一个经典案例 回溯算 ...