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.

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

问题解析:

设计一个类,该类能够实现随机返回链表中的一个结点。如果链表的结点数量非常的多,或者其长度是未知的,或者结点是从一个流中出现的,则如何做到以同样的概率随机返回一个结点的值。

链接:

思路标签

蓄水池算法

解答:

  • 从题目可知,该问题是蓄水池算法的特例:只随机返回一个值;
  • 对于常规的随机算法,我们使用语言自带的随机函数即可实现;但是对于数量居多无法实现内存加载、值从流中输入长度未知的情况,我们无法做到先统计数量再使用随机函数实现,所以就会用到蓄水池算法。
  • 在序列流中取一个数,确保随机性:
    • 假设已经读取n个数,现在保留的数是 A x ,取到 A x 的概率为(1/n);
    • 对于第n+1个数 A n + 1 ,以1/(n+1)的概率取 A n + 1 ,否则仍然取 A x 。依次类推,可以保证取到数据的随机性。
    • 数学归纳法证明如下:
      • 当n=1时,显然,取A1。取A1的概率为1/1。
      • 假设当n=k时,取到的数据 A x 。取 A x 的概率为1/k。
      • 当n=k+1时,以1/(k+1)的概率取 A n + 1 ,否则仍然取Ax。
      • (1)如果取 A n + 1 ,则概率为1/(k+1);
      • (2)如果仍然取 A x ,则概率为(1/k)*(k/(k+1))=1/(k+1);(相当于在取 A x 的基础上,不取 A n + 1
  • 所以,对于所有流入的数,均是以1/(n+1)的概率取到,都是等概率的。
  • 数据流取一个数的代码:
//在序列流中取一个数,保证均匀,即取出数据的概率为:1/(已读取数据个数)
void RandNum(){    
    int res=0;
    int num=0;
    num=1;
    cin>>res;

    int tmp;
    while(cin>>tmp){
        if(rand()%(num+1)+1>num)
            res=tmp;
        num++;
    }
    cout<<"res="<<res<<endl;
}
  • 在序列流中取k个数,确保随机性
    • 建立一个数组,将序列流里的前k个数,保存在数组中。(也就是所谓的”蓄水池”)
    • 对于第n个数 A n ,以k/n的概率取 A n 并以1/k的概率随机替换“蓄水池”中的某个元素;否则“蓄水池”数组不变。依次类推,可以保证取到数据的随机性。
    • 数学归纳法证明如下:
      • 当n=k时,显然“蓄水池”中任何一个数都满足,保留某个数的概率为k/k=1;
      • 假设当n=m(m>k)时,“蓄水池”中每个元素被采样的概率等于k/m;
      • 那么对于第m+1个元素,其被抽样的概率为k/(m+1);
      • 对于已经在蓄水池中的m个元素,每个元素被抽样的概率由两部分组成:
        • (1) 如果第m+1个元素未被采样,则此部分的概率为(k/m)∗(1−k/(m+1));
        • (2) 如果第m+1个元素被采样,但是在蓄水池中的元素未被第m+1个元素替换,则此部分概率为:(k/m) ∗ (k/(m+1)) ∗ (k−1/k)
      • 将上面的两部分相加,即得蓄水池算法池中已有元素被抽样的概率为:

k m ( 1 k m + 1 ) + k m k m + 1 k 1 k = k m + 1

  • 所以,对于流入的m+1个数,均是以k/(m+1)的概率取任意一个数,都是等概率的。
  • 随机选取k个数的代码:
//在序列流中取n个数,保证均匀,即取出数据的概率为:n/(已读取数据个数)
void RandKNum(int n){
    int *myarray=new int[n];
    for(int i=0;i<n;i++)
        cin>>myarray[i];

    int tmp=0;
    int num=n;
    while(cin>>tmp){
        if(rand()%(num+1)+1<n)    
            myarray[rand()%n]=tmp;
    }

    for(int i=0;i<n;i++)
        cout<<myarray[i]<<endl;
}
  • 本题未知长度链表List的解法:
/**
 * 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), listSize(0){
    }

    /** Returns a random node's value. */
    int getRandom() {
        int res = head->val;
        ListNode* pNode = head->next;
        int i = 2;
        while(pNode != nullptr){
            int j = rand()%i;
            if(j == 0)
                res = pNode->val;

            pNode = pNode->next;
            i++;
        }
        return res;
    }

private:
    ListNode* head;
    int listSize;
    int random(int x){
        return rand()%x;
    }
};

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

猜你喜欢

转载自blog.csdn.net/Koala_Tree/article/details/80339379