LeetCode:链表随机节点

题目:

给定一个单链表,随机选择链表的一个节点,并返回相应的节点值。保证每个节点被选的概率一样。

进阶:
如果链表十分大且长度未知,如何解决这个问题?你能否使用常数级空间复杂度实现?

示例:

// 初始化一个单链表 [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。

代码,略。

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值