我要好offer之 機率題大總結

1. 利用等機率Rand5生成等機率Rand3

Rand5生成等機率Rand3html

這個題目能夠擴展爲:利用等機率RandM生成等機率RandN (M > N)數組

 

這裏,咱們首先明白一個簡單的知識點:若是 randN 能夠等機率生成 [0, N) 之間的數dom

假設 N = T的整倍數,那麼咱們能夠直接使用 RandN() / (N / T) 來等機率生成 [0, T)之間的數函數

好比說,Rand21能夠等機率生成[0, 21)之間的數,那麼 Rand21() / 3 就能夠等機率生成 [0, 7)之間的數,即 實現了 Rand7函數優化

 

// 利用 Rand5 實現 Rand3
int Rand3() {
    int tmp = Rand5();
    while (tmp >= 3) {
        tmp = Rand5();
    }
    return tmp;
}

 

證實:以輸出0爲例,看看機率是多少。ui

x的第一個有效數值是經過Rand5獲得的。Rand5返回0的機率是1/5,若是這事兒發生了,咱們就獲得了0,不然只有當Rand5返回3或4的時候,咱們纔有機會再次調用它來獲得新的數據。spa

第二次調用Rand5以後,又是有1/5的機率獲得0,2/5的機率獲得3或4致使循環繼續執行下去,如此反覆。code

所以機率的計算公式爲:htm

 

優化一下:咱們在開始就說明了一個常識,這裏,若是RandM 等機率 生成 RandN 時,當 M 很大 而 N 很小時,這時不少狀況是在while裏循環,可能一直循環blog

所以,我麼把while循環的邊界調整一下

int RandN() {
    int t = N;  // t是小於M的,N的最大倍數
    while (t + N < M) {
        t = t + N;
    }
    int tmp = RandM();
    while (tmp >= t) {
        tmp = RandM();
    }
    return tmp / (t / N);
}

 

2. 利用等機率Rand3生成等機率Rand5

Rand3生成等機率Rand5

這個題目能夠擴展爲:利用等機率RandM生成等機率RandN (M < N)

這裏,咱們要首先 說明一個知識點:

咱們能夠利用 等機率RandM函數 生成 等機率 Rand(M * M)

好比說:M = 5

step1. Rand5 能夠等機率生成 0, 1, 2, 3, 4 (1/5機率)

step2. 設 上一步Rand5的結果爲t,則 mul = M * t 等機率生成:05101520 (1/5機率)

step3. 再次調用 Rand5 等機率生成 0, 1, 2, 3, 4 ,設結果爲 s (1/5機率)
step4. 將step2 和 step3的兩個結果相加, 就能夠等機率生成
012...24 (相乘事件,1/25機率)

 

這樣就實現了 Rand25

 

由於這一題中M < N, 而咱們能夠直接利用 RandM 來 等機率生成 Rand(M * M) ,這時 M * M 極可能就大於N了,若是不大於N,那麼再次 翻倍便可

假設 M * M 大於 N,聯想到第一題,相信你們很容易給出第二題的解了

int RandN() {
    int t = N;  // t是小於M×M的,N的最大倍數
    while (t + N < M * M) {
        t = t + N;
    }
    int tmp = RandM() * M + RandM(); // 等機率生成 Rand(M*M)
    while (tmp >= t) {
        tmp = RandM();
    }
    return tmp;
}

 

3. 單次遍歷,等機率隨機選取1個

(未知行數文件的一行文本/未知長度單鏈表的一個結點)

等機率隨機選取1個

問題描述:假設咱們有一堆數據(可能在一個單鏈表裏,也可能在文件裏),數量未知。

要求只遍歷一次這些數據,隨機選取其中的一個元素,任何一個元素被選到的機率相等。

O(n)時間,O(1)輔助空間(n是數據總數,但事先不知道)

 

解題思路:若是元素總數爲n,那麼每一個元素被選到的機率應該是1/n。然而n只有在遍歷結束的時候才能知道,在遍歷的過程當中,n的值還不知道,能夠利用乘法規則來逐漸湊出這個機率值

 

void GetRandomSingleLine(FILE* fp, char* randomLine) {
    srand(time(NULL));
    int i = 1;
    char tmp[MAX_LINE];
    while (fgets(tmp, sizeof(tmp), fp) != NULL) {
        if (rand() % i == 0) {   // rand() % i == 0 的機率爲 1/i
            strcpy(randomLine, tmp);
        }
        ++i;
    }
}

 

當文件行數爲1時,rand() % 1確定爲0,直接輸出

當文件行數爲2時,rand() % 2 等於0 的機率爲 1/2,此時 輸出第一行 和 第二行 的機率均爲 1/ 2

當文件行數爲3時:

輸出第一行的機率是:1 * (1 - 1/2) * (1 - 1/3) = 1/3

輸出第二行的機率是:1 * (1/2) * (1 - 1/3) = 1/3

輸出第三行的機率是:1 - (1/3 + 1/3) = 1/3 (注意:題目要求必須輸出一行)

 

 

 

4. 單次遍歷,等機率隨機選取k個:蓄水池抽樣問題

(未知行數文件的K行文本/未知長度單鏈表的K個結點)

等機率隨機選取k個(蓄水池抽樣)

解題思路:先讀入第 1 ~ k 行保存,之後每次讀入第 行,都以 k / i 的機率把剛讀入的一行隨機替換以前保存的 行中的一行。

void GetRandomLines(FILE* fp, char** randomLine, int k) {
    srand(time(NULL));

    char tmp[MAX_LINE];
    // 讀入文件第1到k行
    for (int i = 0; i < k; ++i) {
        fgets(randomLine[i], MAX_LINE, fp);
    }

    int i = k + 1;
    while (fgets(tmp, MAX_LINE, fp) != NULL) {
        if (rand() % i < k) {
            // 隨機替換已有的某一行
            int j = rand() % k;
            strcpy(randomLine[j], tmp);
        }
        ++i;
    }
}

  

 

5. 等機率隨機排列數組(隨機洗牌 std::random_shuffle)

隨機洗牌

問題描述:假設有一個數組,包含n個元素。如今要從新排列這些元素,要求每一個元素被放到任何一個位置的機率都相等(即1/n),

而且直接在數組上重排(in place),不要生成新的數組。用O(n) 時間、O(1)輔助空間。

 

解題思路:

先想一想若是能夠開闢另一塊長度爲n的輔助空間時該怎麼處理,顯然只要對n個元素作n次(不放回的)隨機抽取就能夠了。

先從n個元素中任選一個,放入新空間的第一個位置,而後再從剩下的n-1個元素中任選一個,放入第二個位置,依此類推。

 

按照一樣的方法,但此次不開闢新的存儲空間。第一次被選中的元素就要放入這個數組的第一個位置,但這個位置原來已經有別的(也可能就是這個)元素了,這時候只要把原來的元素跟被選中的元素互換一下就能夠了。很容易就避免了輔助空間。

void Random(std::vector<int>& num) {
    srand(time(NULL));
    for (int i = num.size() - 1; i >= 0; --i) {
        int pos = rand() % (i + 1);
        std::swap(num.at(i), num.at(pos));
    }
}

 

6. 利用不均勻硬幣產生等機率

利用不均勻硬幣產生等機率

問題描述:有一枚不均勻的硬幣,已知拋出此硬幣後,正面向上的機率爲p(0 < p < 1)。請利用這枚硬幣產生出機率相等的兩個事件

某一次拋出硬幣,正面向上的機率是p,反面向上的機率是1 - p,當p不等於0.5時,這兩個事件的機率就不同了。

怎麼能湊出等機率呢?仍是要利用機率的加法和乘法法則。這裏用乘法,也就是連續的獨立事件。

連續拋兩次硬幣,正反面的出現有四種狀況,機率依次爲:

兩次均爲正面:p * p 第一次正面,第二次反面:p * (1 - p) 第一次反面,第二次正面:(1 - p) * p 兩次均爲反面:(1 - p) * (1 - p)

中間兩種狀況的機率是徹底同樣的。因而問題的解法就是連續拋兩次硬幣,若是兩次獲得的相同則從新拋兩次;

不然根據第一次(或第二次)的正面反面狀況,就能夠獲得兩個機率相等的事件

 

擴展:

這一題實質就是 利用不均勻機率事件 生成 [0,1]之間的隨機數

如何利用 不均勻機率事件 生活[0,n)之間的隨機數呢?(即0,1,2... n-1這n個數)

咱們每一輪都是連續拋n次硬幣

每一輪中 正面出現次數爲i,則反面出現次數爲 n-i,相應機率爲 C(n, i) * (Pi) * (1-p)(n-i)

當i=1或者n-1時,上述機率分別爲 n*p*(1-p)(n-1) 和 n*p(n-1)*(1-p)

注意到上面2個機率的係數都是n,咱們如今須要隨機生成n個數,咱們就能夠將這兩點聯繫起來

例如i=1,對應n種狀況,咱們將這n種狀況對應編號 n個數

對於每一輪中正面出現次數不是1的時候,就從新再拋一輪

相關文章
相關標籤/搜索