【数学】力扣382:链表随机节点(法2不太懂)

给你一个单链表,随机选择链表的一个节点,并返回相应的节点值。每个节点 被选中的概率一样 。
实现 Solution 类:
Solution(ListNode head) 使用整数数组初始化对象。
int getRandom() 从链表中随机选择一个节点并返回该节点的值。链表中所有节点被选中的概率相等。
示例:

image
输入
["Solution", "getRandom", "getRandom", "getRandom", "getRandom", "getRandom"]
[[[1, 2, 3]], [], [], [], [], []]
输出
[null, 1, 3, 2, 2, 3]
解释
Solution solution = new Solution([1, 2, 3]);
solution.getRandom(); // 返回 1
solution.getRandom(); // 返回 3
solution.getRandom(); // 返回 2
solution.getRandom(); // 返回 2
solution.getRandom(); // 返回 3
// getRandom() 方法应随机返回 1、2、3中的一个,每个元素被返回的概率相等。

不同于数组,在未遍历完链表前,我们无法知道链表的总长度。
方法1:暴力解法
在初始化时,用一个数组记录链表中的所有元素,这样随机选择链表的一个节点,就变成在数组中随机选择一个元素。

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
import random
class Solution:

    def __init__(self, head: Optional[ListNode]):
        self.arr = []
        while head:
            self.arr.append(head.val)
            head = head.next

    def getRandom(self) -> int:
        return choice(self.arr)

# Your Solution object will be instantiated and called as such:
# obj = Solution(head)
# param_1 = obj.getRandom()

时间复杂度:初始化为 O(n),随机选择为 O(1),其中 n 是链表的元素个数。
空间复杂度:O(n)。我们需要 O(n) 的空间存储链表中的所有元素。

方法2:蓄水池抽样/水塘抽样
如果链表非常大且长度未知,此时内存根本无法一次装载在数据(更别提用再开辟一个列表专门存节点值),此时方法1基本完全失效,可以用蓄水池抽样方法:
遍历一次链表,在遍历到第 m 个节点时,有 \(\frac{1}{m}\) 的概率选择这个节点来覆盖掉之前的节点选择。则将节点 m 的值存入答案,最后一次覆盖答案的节点即为本次抽样结果。
算法随机性的证明:对于长度为 n 的链表的第 m 个节点,最后被采样的充要条件是它被选择,且之后的节点都没有被选择。这种情况发生的概率为 $ \frac{1}{m} × \frac{m}{m+1} × \frac{m+1}{m+2} × ··· × \frac{n-1}{n} = \frac{1}{n} $。因此每个点都有均等的概率被选择。
image


时间复杂度:初始化为 O(1),随机选择为 O(n),其中 n 是链表的元素个数。
空间复杂度:O(1)。我们只需要常数的空间保存若干变量。

总结:有限数据的情况下方法1空间换时间更快,但是方法2能解决数据流
image

posted @ 2022-05-11 15:07  Vonos  阅读(85)  评论(0)    收藏  举报