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個數,現在保留的數是Ax ,取到Ax 的概率爲(1/n);
    • 對於第n+1個數An+1 ,以1/(n+1)的概率取An+1 ,否則仍然取Ax 。依次類推,可以保證取到數據的隨機性。
    • 數學歸納法證明如下:
      • 當n=1時,顯然,取A1。取A1的概率爲1/1。
      • 假設當n=k時,取到的數據Ax 。取Ax 的概率爲1/k。
      • 當n=k+1時,以1/(k+1)的概率取An+1 ,否則仍然取Ax。
      • (1)如果取An+1 ,則概率爲1/(k+1);
      • (2)如果仍然取Ax ,則概率爲(1/k)*(k/(k+1))=1/(k+1);(相當於在取Ax 的基礎上,不取An+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個數An ,以k/n的概率取An 並以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)
      • 將上面的兩部分相加,即得蓄水池算法池中已有元素被抽樣的概率爲:

km(1km+1)+kmkm+1k1k=km+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();
 */
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章