题目:
给定一个单链表,随机选择链表的一个节点,并返回相应的节点值。保证每个节点被选的概率一样。
进阶:
如果链表十分大且长度未知,如何解决这个问题?你能否使用常数级空间复杂度实现?
示例:
// 初始化一个单链表 [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()方法应随机返回1,2,3中的一个,保证每个元素被返回的概率相等。 solution.getRandom();
解:
最简单的就是统计个数,然后随机返回一个值回去,但是这样子就得两次遍历,第一次统计个数,第二次获得特定节点值,当然你也可以第一次遍历是把链表存在ArrayList里,然后直接get,但是这样太浪费内存。
另外的,就是采用蓄水池抽样算法,即第 i 个节点被抽取当作结果的概率为 1/i ,顺序概率抽取、替换,这样我们就不用去统计整体的n,也能保证被选中概率一样。
怎么保证?
数学归纳法:
第一个节点A,1/1的概率作为结果;
第二个节点B,1/2的概率作为结果,那么此时A就有1/2的概率被替换,于是A=1*1/2=1/2的概率不被替换,作为结果;
第三个节点C,1/3的概率作为结果,那么此时A=1/2*2/3=1/3,B=1/2*2/3=1/3概率不被替换,作为结果;
......
第n个节点,1/n的概率作为结果,那么此时A=1/(n-1)*(n-1)/n=1/n,B=...,C=...。
神奇吗?前面的节点的本身留下概率大,但是经过筛选的次数多导致概率下降,所以每一次筛选每个节点被选中概率都是一样的。
代码:
import java.util.Random;
class Solution {
private ListNode ls;
/** @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) {
ls=head;
}
/** Returns a random node's value. */
public int getRandom() {
ListNode tls=ls;
Random rd=new Random(); //随机数随便,保证概率为1/i即可
int i=1,rs=tls.val;
while(tls!=null){
if(rd.nextInt(i++)==0){
rs=tls.val;
}
tls=tls.next;
}
return rs;
}
}
这个算法不止在遍历上节省时间,还可以节省空间,当链表过大时,我们就可以不用一次性读取整个链表,而只需读取单个节点。
另外的,如果抽取的不止一个节点也是一样的,假设抽取m个,m<n:
先确定初始化的m个,每个进入的概率是1;第 [m+1,n] 个时,每个都选取一个 [1,i] 的数k,在 [1,m] 则替换m原组k位置:
第m+1个时,每个被替换的概率m/(m+1)*1/m=1/(m+1),留存概率1*m/(m+1)=m/(m+1);
第m+2个时,每个被替换的概率为m/(m+2)*1/m=1/(m+2),留存概率m/(m+1)*(m+1)/(m+2)=m/(m+2);
...
第n个时,替换概率1/n,留存概率m/n。
代码,略。