概率问题总结
1. 给N张扑克牌和一个随机函数,设计一个洗牌算法 [CC150]
关键点:如何保证每张牌等概率地出现在每个位置
伪码如下
for i in 1…n: randomly select a card j from [1,i] swap card i with card j
C++实现
1 void shuffle(vector<int> cards, int n) { 2 if (cards.empty()) { 3 return; 4 } 5 srand(time(0)); 6 for (int i = 1; i <= n; i++) { 7 int j = 1 + rand() % i; 8 swap(cards[i - 1], cards[j - 1]); 9 } 10 }
用数学归纳法证明其正确性:
1. 当N=1时,P(N张牌到任意位置的概率)= 1/N ; 成立
2. 假设当N = k时成立,那么当N=k+1时,我们只需要判断是否满足每张牌到每个位置的概率都是1/N即可。
这里我们对于判断“每张牌到每个位置”分成两大部分,三小部分,看它们的概率是否是1/N。即:
<1> 第k+1张牌到任意位置
由算法可知,当i=k+1时,从1~i中随机选出一个位置j,将其与i位置的元素互换。 由此可知,第k+1张牌到任意位置的概率都是相等的,即都是1/(k+1); 同理可知,<2.1>也得证。即前k张牌到第k+1位置的概率也都是相等的。
<2> 前k张牌到任意位置
<2.1> 前k张牌到第k+1位置
<2.2> 前k张牌到前k个位置(即前k张牌中任一张牌到前k个位置中的任一位置)
这里需要用到贝叶斯公式,即条件概率。
这里我们要求的实际上是前k张牌中任一张牌X到前k个位置中的任一位置index的概率,如果等于1/(k+1)即得证。
令事件A为这张牌X不被换到第k+1位置处,事件B为牌X被换到前k个位置中的某位置index
已知 P(B|A) = 1/k , P(A) = 1 - (1 / (k+1))
则 P(B) = P(B|A)*P(A) + P(B|~A)P(~A)
= 1/k * [1-(1/(k+1))] + 0
= 1/(k+1)
拓展: 完美洗牌算法 ref
2. 如何等概率地从n个数中随机抽出m个数?[CC150]
变形:如何从1亿个qq号中随机抽出100个qq号?
如何从一天的query log里随机抽出100个query?
我们需要保证每个位置都以m/n的概率被取出。【位置:从1-n这所有的数,每个数对应一个位置】
方法:抽签法
第 i 个位置以 (m - k)/(n - i + 1) 的概率决定当前数是否选(其中k为前面已经抽出的数的个数)
证明:
第1个位置:被选中的概率为(m - 0)/ (n - 1 + 1) = m/n
第2个位置:(n-m)/n * (m/(n-1)) + (m/n) * ((m-1)/(n-1)) = m/n
...
总之,我们能保证让每个位置被选中的概率都是m/n.
实现的时候可以p=rand(0, 1), 若p>=(m - k)/(n - i + 1)则选择该数,否则不选。
1 void chooseEvenly(vector<int>& nums, int m) { // (m - k) / (n - i + 1) 2 int n = nums.size(); 3 int k = 0; 4 for (int i = 0; i < n; i++) { 5 int x = rand(1, n - i); 6 if (x < m - k) { 7 cout << nums[i]; 8 k++; 9 } 10 } 11 }
follow up:
2.1 如果n大小不确定,如何随机抽出k个数?
变形: 输入的是一个数据流,无法预估其大小
方法:蓄水池采样法 ref
设置一个k大小的蓄水池,对前k个数直接放入蓄水池,从第k+1个数开始,每个数都以 k/i 的概率放入蓄水池并从池里随机替换出一个数。
伪码:
Init : a reservoir with the size: k for i= k+1 to N M=random(1, i); if( M < k) Swap the Mth value and ith value end for
3. 给定一个能够生成0,1两个数的等概率随机数生成器”,如何生成一个产生0,1,2,3的等概率随机数生成器?
方法:级联法
将两个0,1 随机生成器级联,每次产生两个数,则可能的结果有(0,0), (0,1), (1,0), (1,1),分别映 射到0, 1, 2, 3即可
follow up:
3.1 如何用rand7生成rand9?
两个rand7可以产生49种可能,扔掉后面的4种,保留前45个,并平均分成9份。
每次产生一个结果时,假如没落在对应区间中就扔掉,否则根据落在哪个区间判断是0~8中哪个。
3.2 有1枚硬币,以p的概率产生正面,以1-p的概率产生背面,如何利用它产生一个0.5概率的生成器?
将两枚硬币级联,只保留“正反”,“反正”两种结果,其他两种结果扔掉
4. A,B,C三人轮流扔硬币,第一个扔到正面的人算赢,问三个人赢的概率分别为多大?
方法:方程法
对这种n不确定或有可能无限循环的题,可以考虑化大为小、列方程。
P(A) = (1/2)^1 + (1/2)^4 + (1/2)^7 + ...
P(B) = (1/2)^2 + (1/2)^5 + (1/2)^8 + ...
P(C) = (1/2)^3 + (1/2)^6 + (1/2)^9 + ...
所以,
P(B) = 1/2*P(A) ,P(C) = 1/4 * P(A); P(A) + P(B) + P(C) = 1;
可得 P(A) = 4/7, P(B) = 2/7, P(C) = 1/7
5. A有n个硬币,B有n+1个硬币,谁丢的正面多谁赢,问A不输的概率?
对前n轮而言,有3种情况:A>B, A<B, A=B.
设P(A>B) = x, P(A == B) = y,由对称性P(A<B) = x,则有2x + y = 1
现在来看B扔最后一个硬币的情况:
• 假如之前A>B,则无论怎么扔,A都不会输,最多平. 概率为x
• 如果A==B,则B扔了正面,A才会输,这是0.5y;则A不输的概率也为0.5y
• 如果A<B,则无论B怎么扔,A都输
所以A不输的概率是:x + 0.5y = 0.5 * (2x + y) = 0.5
6. 一个机器人在原点,右边有一个距离为k的点,机器人以p的概率右移一步,1-p概率左移一步, 问经过M步机器人处于k点的概率?
k步右移,剩下的M - k步一半左移一半右移,所以
若 M<k 或 (M - k)%2 == 1 ,则概率为0
其他情况下:M中选k + (M-k)/2步的概率
7. 扔硬币直到连续两次出现正面,求扔的期望次数
假设期望次数是E,我们开始扔,有如下几种情况:
• 扔到的是反面,那么就要重新扔,所以是0.5*(1 + E) 【从头来过,递归的继续扔,从这时起期望仍是E】
• 扔到的是正面,再扔一次又反面了,则是0.25*(2 + E)
• 扔连续两次都是正面,结束,则是0.25*2
所以递归来看E = 0.5*(1 + E) + 0.25*(2 + E) + 0.25*2,解得E = 6