LeetCode 382. Linked List Random Node 解题报告

LeetCode 382. 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.


示例

// 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();


限制条件

What if the linked list is extremely large and its length is unknown to you? Could you solve this efficiently without using extra space?


解题思路

我的思路:

不考虑限制条件,很直观的就是设置一个变量size,用于存储链表的大小。生成一个范围在[0, size]内的随机数random,从表头开始移动random个位置,返回对应节点存储的值。
然而题目说了这个链表的长度很大,我们无法获得它的具体长度,同时不能使用额外的存储空间。其实就是不允许使用变量存储链表的长度,或是使用其他的STL存储链表的元素。
苦思之后,我不知道怎么办,还是惭愧地点开了LeetCode里这道题目的tag,是Reservoir Sampling。是一个我没有听过的算法,经过查资料学习,下面给出这道题正确的解法。

参考思路:

Reservoir Sampling是水塘抽样算法(又叫蓄水池抽样算法)。这个算法用在这道题上的思想是:在具有 n 个元素的链表中,对于第m个元素,以 1m 的概率选择它,有 m1m 不被选择。这样每一个元素被选中的概率都是 1n 。证明如下:

m 个元素最后被选中的概率 = m被选中的概率 × m 之后的元素都不被选中的概率。
p(m)=1m×(mm+1×m+1m+2×...×n2n1×n1n)=1n

可能有人会想:不用考虑 m 之前的元素么?是不用的,因为不管前面的元素是否被选中,只要m被选中而且之后的元素都不被选中的话,那么最后选择的元素就会是 m

在具体实现时,我们可以通过rand()%m得到范围为 [0,m] 的一个随机数。选择0作为标准,0在 [0,m] 中的概率是 1m ,所以 rand()%m==0 就相当于以 1m 选择第 m 个元素。程序的结束条件是遍历完所有的n个元素。


代码

我的代码
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    /** @param head The linked list's head.
        Note that the head is guaranteed to be not null, so it contains at least one node. */
    Solution(ListNode* head): head(head), size(0) {
        for(ListNode *cur = head; cur; cur = cur->next) {
            size++;
        }
    }

    /** Returns a random node's value. */
    int getRandom() {
        int random = rand() % size;
        ListNode *cur = head;
        for (int i = 0; i < random; i++) {
            cur = cur->next;
        }
        return cur->val;
    }

private:
    ListNode *head;
    int size;
};

/**
 * 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.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    /** @param head The linked list's head.
        Note that the head is guaranteed to be not null, so it contains at least one node. */
    Solution(ListNode* head): head(head) {}

    /** Returns a random node's value. */
    int getRandom() {
        int random = head->val;
        ListNode *cur = head->next;
        for (int i = 2; cur; i++) {
            // choos i with a probability of 1/i
            if (rand() % i == 0) 
                random = cur->val;
            cur = cur->next;
        }
        return random;
    }

private:
    ListNode *head;
};

/**
 * Your Solution object will be instantiated and called as such:
 * Solution obj = new Solution(head);
 * int param_1 = obj.getRandom();
 */

总结

今天学到了一个之前没有接触过的新算法,感觉收获蛮大的,很开心呐。不过如果是我自己能够想出这种解法就更棒了(好吧, 我想多了。。。)。下面列出一些关于Reservoir Sampling的资料:
1.huagong_adu: 蓄水池抽样——《编程珠玑》读书笔记
2.数据工程师必知算法:蓄水池抽样
终于填好了这个坑,不容易,继续加油!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值