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.
Follow up:
What if the linked list is extremely large and its length is unknown to you? Could you solve this efficiently without using extra space?
Example:
// Init a singly linked list [1,2,3].
ListNode head = new ListNode(1);
head.next = new ListNode(2);
head.next.next = new ListNode(3);
Solution solution = new Solution(head);
// getRandom() should return either 1, 2, or 3 randomly. Each element should have equal probability of returning.
solution.getRandom();
初始想法,采用Math.random()
函数,但是后期提交有例子未通过,遂改为利用Random类提供随机数,提交通过,但是这种方法需要知道数链长度且空间要求高。Math和Random的两种随机方法建议采用Random类的随机方法,具体原因可见:Math.random() versus Random.nextInt(int)
故改为第二种方法:水塘抽样法。该方法的原理可参见维基百科,数据工程师必知算法:蓄水池抽样,Brief explanation for Reservoir Sampling。具体思路如下:
水塘容量k即要保留的数据,也可以理解为采样值。在本题为1。
现在考虑1个输入,则水塘保留,故保留概率P=1/1;考虑2个输入,水塘只能保留1个,所以对于新加入的“2”保留其的概率为1/2;现在考虑3个输入,保留“3”的概率为1/3,这样对于3个输入来说,3的保留概率为1/3正好,现在看看三输入下的前两个数字被保留的概率,对“1”和“2”来说,前一轮被保留的概率为1/2,这一轮被保留的概率为(1-1/3=2/3),所有总的保留概率为1/2×2/3=1/3。正好满足概率均等。所以推论,对k=1的情况,第i个数保留概率为1/i。
- 1个输入 1(1/1)
- 2个输入 1,2(1/2)
- 3个输入 1,2(删去),3(1/3)
以上就是该题的解题思路,再拓展一下,对于要保留(采样)k个数据,推论第i个数保留概率为k/i,例子(k=2)如下:一个数,200%保留;两个数100%保留;三个数,保留“3”概率为2/3;”1”保留的概率为:1/3(“3”被舍弃即”1,2“保留概率)+2/3(“3”保留下来时概率)×1/2(“1,2”挑一个踢走)=2/3,同理得“2”的概率为2/3;四个数,“4”为2/4,剩下两个数的都为:2/3(上轮保留概率)×(2/4(“4”被踢走概率)+2/4(“4”留下概率)×1/2(二选一被踢走概率))=2/4=1/2;总结起来就是:当进行到第i个数时,第i个数的概率为 k/i ,之前数保留概率为 [k/(i−1)]∗[(1−k/i)+(k/i)∗[(k−1)/k]]=k/i
- 1个输入 1(2/1)
- 2个输入 1,2(2/2)
- 3个输入 1,2(删去),3(2/3)
- 4个输入1(删去),2(删去),3,4(2/4)
初始思想的算法如下:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
ArrayList<Integer> list;
/** @param head The linked list's head.
Note that the head is guaranteed to be not null, so it contains at least one node. */
public Solution(ListNode head) {
list = new ArrayList<>();
ListNode i = head;
while(i != null){
list.add(i.val);
i = i.next;
}
}
/** Returns a random node's value. */
public int getRandom() {
int size = list.size();
Random random = new Random();
int index = random.nextInt(size);
return list.get(index);
}
}
/**
* Your Solution object will be instantiated and called as such:
* Solution obj = new Solution(head);
* int param_1 = obj.getRandom();
*/
水塘算法如下:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
ListNode h;
/** @param head The linked list's head.
Note that the head is guaranteed to be not null, so it contains at least one node. */
public Solution(ListNode head) {
h = head;
}
/** Returns a random node's value. */
public int getRandom() {
ListNode cur = h;
int res = cur.val;
Random random = new Random();
for(int count = 1; cur != null; count++){
if(random.nextInt(count) == 0) res = cur.val;
cur = cur.next;
}
return res;
}
}
/**
* Your Solution object will be instantiated and called as such:
* Solution obj = new Solution(head);
* int param_1 = obj.getRandom();
*/