Linked List Random Node

743 阅读2分钟

Linked List Random Node

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.

Implement the Solution class:

  • Solution(ListNode head) Initializes the object with the head of the singly-linked list head.
  • int getRandom() Chooses a node randomly from the list and returns its value. All the nodes of the list should be equally likely to be chosen.

Example 1:

image.png

 Input
 ["Solution", "getRandom", "getRandom", "getRandom", "getRandom", "getRandom"]
 [[[1, 2, 3]], [], [], [], [], []]
 Output
 [null, 1, 3, 2, 2, 3]
 ​
 Explanation
 Solution solution = new Solution([1, 2, 3]);
 solution.getRandom(); // return 1
 solution.getRandom(); // return 3
 solution.getRandom(); // return 2
 solution.getRandom(); // return 2
 solution.getRandom(); // return 3
 // getRandom() should return either 1, 2, or 3 randomly. Each element should have equal probability of returning.

Constraints:

  • The number of nodes in the linked list will be in the range [1, 104].
  • -104 <= Node.val <= 104
  • At most 104 calls will be made to getRandom.

解法一:模拟、暴力

做法:

将链表的所有元素取出来,形成一个数组,在数组随机取数即可。

代码:

 /**
  * Definition for singly-linked list.
  * type ListNode struct {
  *     Val int
  *     Next *ListNode
  * }
  */
 type Solution struct {
     nums []int
 }
 ​
 ​
 func Constructor(head *ListNode) Solution {
     nums:=[]int{}
     for head!=nil{
         nums=append(nums,head.Val)
         head=head.Next
     }
     return Solution{nums}
 }
 ​
 ​
 func (this *Solution) GetRandom() int {
     return this.nums[rand.Intn(len(this.nums))]
 }
 ​
 ​
 /**
  * Your Solution object will be instantiated and called as such:
  * obj := Constructor(head);
  * param_1 := obj.GetRandom();
  */

解法二:蓄水池抽样

具体做法为:从前往后处理每个样本,每个样本成为答案的概率为 \frac{1}{i},其中 i 为样本编号(编号从 111 开始),最终可以确保每个样本成为答案的概率均为 \frac{1}{n} (其中 n 为样本总数)。

证明:

目前已经有了 k 个元素,选中的概率是 \frac{1}{k} ,基于此加入更多的元素,最后全部元素中样本选择概率是 \frac{1}{n} (其中 n 为样本总数)。公式如下:

P=1kκk+1κ+1k+2...n1nP=\frac{1}{k}*\frac{\kappa}{k+1}*\frac{\kappa+1}{k+2}*...*\frac{n-1}{n}

经过化简可以得:

P=1nP=\frac1n

实现代码如下:

 /**
  * Definition for singly-linked list.
  * type ListNode struct {
  *     Val int
  *     Next *ListNode
  * }
  */
 type Solution struct {
     head *ListNode
 }
 ​
 ​
 func Constructor(head *ListNode) Solution {
     return Solution{head}
 }
 ​
 ​
 func (this *Solution) GetRandom() int {
     ans:=0
     for idx,head:=1,this.head; head!=nil;idx++{
         if rand.Intn(idx)==0{
             ans= head.Val
         }
         head=head.Next
     }
     return ans
 }
 ​
 ​
 /**
  * Your Solution object will be instantiated and called as such:
  * obj := Constructor(head);
  * param_1 := obj.GetRandom();
  */

问题一:为什么需要 rand.Intn(idx)==0 这个判断条件?

理解:因为是从第一个元素开始的,所以他的随机区间是 [0,1),所以是 需要等于 0。

问题二:为什么需要从一开始处理样本到最后?这里和公式哪里是一致的。

举个例子:

假设有 10 个元素,需要随机选择一个元素,在第五次处理元素时候,随机数值是 0,代表该选择的概率是 P=\frac{1}{5}\frac{1}{6}\frac{1}{7}...\frac{1}{10},最后计算可得概率是 \frac1{10}。如果将这一次的过程扩展到每一个元素呢?是不是每次随机到的元素的值是 \frac1{10}

接下来如果希望把这个定理进行扩展的话,定理描述如下:

参与样本总数量是 N,抽取样本数为 K,每个样本抽取的概率是 \frac{K}{N}。

参考如下:

蓄水池抽样算法——原理、实现与应用