[382]. 链表结点
Posted Debroon
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[382]. 链表结点相关的知识,希望对你有一定的参考价值。
[382]. 链表结点
题目
传送门:https://leetcode.cn/problems/linked-list-random-node/
算法设计:数组取摸
思路:单链表不能随机访问,数组才可以。所以,我们用一个数组保存链表数据,再对数组求模实现随机选取。
class Solution
vector<int> arr;
public:
Solution(ListNode *head)
while (head)
arr.push_back(head->val); // 数组保存
head = head->next;
int getRandom()
return arr[rand() % arr.size()]; // 随机选取
;
时间复杂度: θ ( n ) \\theta(n) θ(n)
空间复杂度:
θ
(
n
)
\\theta(n)
θ(n)
算法设计:蓄水池抽样
蓄水池抽样,是一种用于解决数据流的多次公平随机采样算法。
- 多次、等概率、随机采样:字面意思,拒绝采样、洗牌算法也可以实现。
- 数据流:不是一次性给出所有数据,而是一个数据流,不知道到底有多少数据。蓄水池算法的特点,在于数据流上。
数据是不固定的,蓄水池抽样的思路是,建立一个能存储 k 个元素的蓄水池。
k 是可以动态调整的,可大可小。
步骤:
- 从数据流中,放入 k 个元素进入蓄水池 [0, k-1]
- 从数据流中第 k + 1 个数据开始算,处理第 i (k + 1)个元素,随机生成 [0, i-1] 之间的索引 j
- 如果 j 在 [0, k-1] 之间,则替换蓄水池[j],否则,扔掉
这样就能保证,每个元素都会以等概率( k k + 1 \\frackk+1 k+1k)出现在蓄水池中。
【数学证明】(可跳过)
前 k 个数据都在蓄水池内,从第 k + 1 个新数据开始。
处理完第 k+1 个元素,蓄水池相当于见到了 k+1 个元素,每个元素都是以 k k + 1 \\frackk+1 k+1k 出现在蓄水池中。
从数据流中第 k + 1 个数据开始算,在 [0, k] 之间生成一个索引,如果在蓄水池范围内 [0, k-1],则替换蓄水池中的索引位置。
- 新元素出现在蓄水池的概率是: k k + 1 \\frackk+1 k+1k,只有在最后一个索引位置才不能出现在蓄水池
对于天生(前 k 个元素)就处于蓄水池中的元素,每个元素被替换的概率是: 1 k + 1 \\frac1k+1 k+11。
- 反过来说,经过调整后,原本元素都有 k k + 1 \\frackk+1 k+1k 的概率继续留在蓄水池
所以说,每个数据出现在蓄水池的概率是 k k + 1 \\frackk+1 k+1k。
class Solution
ListNode *head;
public:
Solution(ListNode *head)
this->head = head;
int getRandom()
int i = 1;
// i 用于记录新的元素的索引,i = 1 表示蓄水池只能存储 1 个元素
int ans = head->val;
// 提前把第 1 个元素放入蓄水池
for (auto node = head->next; node != nullptr; node = node->next, i ++)
// 新元素是从数据流第 2 个元素开始取,如果链表不为空,再接着取下一个元素 | 新取元素索引 + 1,记录目前是第几个元素
int j = random() % (i + 1);
// 随机生成 [0, k] 之间的索引
if ( j == 0 ) ans = node->val;
// 如果 j 处于蓄水池范围内,这里的蓄水池容量为 1,范围是 [0, 0]
return ans;
;
时间复杂度: θ ( 1 ) \\theta(1) θ(1)
空间复杂度: θ ( 1 ) \\theta(1) θ(1)
以上是关于[382]. 链表结点的主要内容,如果未能解决你的问题,请参考以下文章