反直覺的三門問題:Python告訴你,為什麼80%的人都錯了
- 2019 年 12 月 2 日
- 筆記
作者:吹牛Z
來源:數據不吹牛(ID:shujubuchuiniu)
01 什麼是「三門問題」?
我,莫名其妙的出現在舞台中央,所有聚光燈齊齊射來,突如其來的強光刺的我睜不開眼,只聽見場下震耳欲聾的吶喊「堅持你的選擇!堅持你的選擇!」

我趕緊捋了捋腦中殘存的思緒,原來,我穿越到另一個有獎闖關者身上。他一路披荊斬棘,KO掉來自全國的所有競爭對手,此刻,正面臨最後一個挑戰,眼前5000萬大獎唾手可得,萬萬沒想到被我給穿(擠)下線了。
最後一關,是一個叫做「決定」的挑戰,獎金高達5000萬元:




生存,還是毀滅,這是一個問題。換,還是不換,這也是一個問題。 乍一看,第一步就是盲選啊,而且被我擠下線的那個倒霉蟲已經遵循「什麼不會就選C」的選擇題黃金定律選擇了C門。
此刻,主持人友善的打開了B門,門後空空如也,然後狡黠一笑,挑著眉問:「先生,你是堅持最初的選擇C呢?還是投向A門的懷抱?」
「堅持你的選擇!不要臨陣變卦!」觀眾嘶聲力竭。

我有點懵逼,畢竟沒怎麼經歷過這麼大的陣仗,趕緊緊閉雙眼,試圖讓注意力集中,嘴裡念念有詞的回憶當年做選擇題的要訣:
- 三長一短就選短
- 三短一長就選長
- 參差不齊就選D
- 三個選項就蒙C
從經驗的角度來看,蒙C的確是目前的最優選項啊!我清了清嗓子,篤定的開腔」我堅持我最初的選擇!」
主持人笑出了魚尾紋,優雅的打開了所有的門,C門後空空如也,而A門後則是金光閃閃的終極大獎。
擦!大獎擦擦擦擦肩而過!
「看吶!你終於為自己的固執付出代價了!」觀眾瞬間換了腔調,嘲諷的聲浪幾乎把人吞沒。
我胸口越來越悶,一股氣擰成團堵在嗓子眼,盡全力想呼出救命卻發不出任何聲音,突然,眼前一片漆黑。
02 先說結論:一定要改變最初的選擇!
不知過了多久,再次被強光刺醒,發現自己又回到了舞台中央,再次聽見震耳欲聾的吶喊「堅持你的選擇!堅持你的選擇!」《恐怖游輪》中無線循環的場景闖入腦中,一聲聲卧槽在心中回蕩。
「哥們,是你把我擠下線了把!就你這智商,還想贏大獎?」腦海中另一個聲音無比清晰。
我不禁回應道:「剛才C還不是你選的,我只是錯信了傻X而已」
「告訴你吧,第一次的選擇並不重要,關鍵在於,在主持人幫你打開一扇門之後,一定要改變最初的選擇!」
「扯吧你,改了選項之後的選擇題,一定是錯的,當我不知道考場潛規則嘛!」我是個經驗主義者,對他的話表露出一臉不屑。

「這樣吧,我換個思路給你講清楚,總體邏輯其實是一樣的。
假設你現在面前有1000扇門,只有一扇門背後藏著獎品,你第一次隨便選一個門,然後主持人會幫你在你沒選擇的999扇門中打開998扇沒有獎品的門,現在場上還剩你最初選擇的那個門,和主持人幫你排除之後的一個門,你換不換?」
「這個,當然是換的中獎概率更大!但是我們這個玩法好像不太一樣吧」我似懂非懂。
「三扇門選擇的邏輯也是一樣的!只是樣本量不同而已!」他扯著嗓子怒吼。
「我就是不信,剛才其實只是運氣不好,再來一輪准沒問題!」於是,我又一輪堅持自己的選擇,又一次與大獎擦肩而過,又一次胸悶,又一次回到了重生點。 「哥,我為什麼運氣這麼差!」我被這突如其來的循環穿越搞的近乎絕望,只想儘快逃出這個鬼循環。
「說白了,你不改變選擇,中獎概率只有1/3,一旦改變了選擇,中獎概率就會上升到2/3了」他直接拋出了結論,而且語氣平和了許多,好像已經習慣了我的腦迴路。
看著我緊皺的眉心,他嘆了口氣:「閉上眼睛吧,我帶你到思維的平行世界,用邏輯和Python程式碼給你演示,演到你服為止。」
03 Python演示
在我們面前有3個門,大獎藏在某個門背後,我們給大獎所在的門編個號(1號,2號,3號),在1,2,3之間隨機生成,生成幾就表示大獎在幾號門。要讓你信服,我們會玩很多輪,所以定義一個函數,默認是生成100輪的正確答案(也就是玩100次)。
#這裡我們默認是玩100輪 def gen_random(num = 100): #獎品在1,2,3號門中隨機出現 lst = np.random.randint(1,4,size = num) return lst
1. 猜法一,堅持最初的選擇
猜法一就是盲猜,我們從1,2,3個門中隨機猜一個,且不會因為主持人的干擾而改變想法,所以開始猜什麼最終也就是什麼,猜法一比較簡單,我們直接定義一個函數:
#定義基於第一種猜法的函數 def guess_one(num = 100): #生成正確的答案 lst = gen_random(num) #猜的輪數和前面生成的輪數一致,這裡默認玩100次(生成100次正確答案,對應猜100次) guess = np.random.randint(1,4,size = len(lst)) #計算有多少個正確的,相等即為正確,最終返回FALSE和TRUE的序列 judge = (lst == guess) #因為TRUE默認是1,FALSE默認是0,我們直接求和,來計算正確率是多少 correct_rate = judge.sum() / len(judge) print('Bro,這次我們玩%d輪~' % num) print('在第二次不改變選擇的策略下,你最終的中獎率是:%.2f' % (correct_rate * 100))
2. 猜法二,改變最初的選擇
猜法二,先從1,2,3個門中隨機猜一個,當主持人打開一個空門,改變最初的選擇。
無論我們首次的選擇是否中獎,在主持人第一次打開門階段,我們選擇的門不會被打開,背後是大獎的門也不會被打開(有時候可能是同一個)。
先生成數據:
#和上面一樣,隨機生成100次正確答案 lst = gen_random(num) #第一步,依然是隨機猜100次 guess = np.random.randint(1,4,size = len(lst)) #因為第二次我們會改變選擇,這裡創建一個列表來存儲我們改變後的最終選擇 guess_change = []
- 場景一:我們第一次猜的門沒有獎品
如果正確答案和我們第一次猜的不一致,主持人排除掉一個門(空門)之後我們改變選擇,最終選的肯定是正確答案。舉個栗子:當我們選擇的是A門,大獎藏在B門,那主持人幫我們打開的空門一定是C門,然後問我們是否改變選擇。如果我們第一步猜的和正確答案不一致,改變選擇之後一定會中獎。
#循環遍歷每一輪的正確答案和我們第一步猜的結果 for anwser,g in zip(lst,guess): #當答案和我們猜的不一樣,那我們改變選擇之後就是正確答案 if anwser != g: guess_change.append(anwser)
- 場景二:我們第一次就猜對了
當我們猜的門和正確答案一致,主持人隨機打開一扇門之後,我們會選擇剩下一扇未被打開的空門。
繼續舉栗子:如果我們猜的A門,大獎就在A門,那麼真理既然被我們選中,主持人在沒有獎的B和C門中隨機打開一扇都可以,然後問我們是否change,要是B門被打開,那麼剩下A(我們第一步選擇的)和C門,我們改變立場會投向C門的懷抱,結果就是大獎飛走了。
#如果我們第一次就猜到正確答案 if anwser == g: #生成一個正確答案的範圍 anwser_range = [1,2,3] #由於我們猜的就是正確答案,先排除掉(因為最後我們會改變選擇) anwser_range.remove(anwser) #主持人在我們選擇之外隨機打開一個門 anwser_range.remove(anwser_range[random.randint(0,1)]) #剩下的一個就是我們最終選擇的門 guess_change.append(anwser_range[0])
第二個場景我們也可以順手封裝成一個完整函數,這個注釋更加完整:
def guess_two(num = 100): #和上面一樣,隨機生成100次正確答案 lst = gen_random(num) #第一步,依然是隨機猜100次 guess = np.random.randint(1,4,size = len(lst)) #因為第二次我們會改變選擇,這裡創建一個列表來存儲我們改變後的最終選擇 guess_change = [] for anwser,g in zip(lst,guess): #無論是否中獎,在主持人第一次打開門階段,我們選擇的門不會被打開,背後是大獎的門也不會被打開(有時候可能是同一個) #舉個栗子:當我們選擇的是A門,大獎藏在B門,那主持人幫我們打開的空門一定是C門,然後問我們是否改變選擇 #也就是說,當我們第一步猜的和正確答案不一致,改變選擇之後一定會中獎 #anwser是正確答案,g是我們第一步猜的 if anwser != g: #如果正確答案和我們第一次猜的不一致,主持人排除掉一個門之後我們那改變選擇,肯定選的是正確答案——i guess_change.append(anwser) else: #當我們猜的門和正確答案一致,主持人隨機打開一扇門之後,我們會選擇剩下的一扇未被打開的空門 #繼續舉栗子:如果我們猜的A門,大獎就在A門,那麼真理既然被我們選中,主持人在沒有獎的B和C門中隨機打開一扇都可以 #然後問我們是否change,要是B門被打開,那麼剩下A(我們第一步選擇的)和C門,我們改變立場會轉向C門,結果就是大獎飛走了 anwser_range = [1,2,3] #我們選擇的就是正確答案,先排除掉(因為最後我們會改變選擇) anwser_range.remove(anwser) #主持人隨機打開一個門 anwser_range.remove(anwser_range[random.randint(0,1)]) #剩下的一個就是我們最終選擇的門 guess_change.append(anwser_range[0]) #到這一步,我們對剛才改變選擇之後的結果進行匯總 guess_change = np.array(guess_change) #看看猜對了多少輪 judge = (lst == guess_change) #正確率是多少 correct_rate = judge.sum() / len(judge) print('Bro,這次我們玩%d輪~' % num) print('在第二次改變選擇的策略下,你最終的中獎率是:%.2f' % (correct_rate * 100))
接下來,每種猜法分別玩10,100,1000直到1000萬次。
猜法一:

猜法二:

堅持選擇,獲獎概率最終會穩定在33%,改變選擇,最終會穩定在66%。在鐵一般的事實面前,我完全信服了。 看著我錯愕的眼神,他切掉了這個平行世界的程式碼介面,消失前臉上露出了一副「聰明人的快樂,就是這麼樸實無華且枯燥」的表情。

聚光燈又一次打在我的身上,耳畔再次響起了主持人性感的聲音:「先生,您是否改變最初的選擇」。
已經記不清第一步是誰做出的選擇,也不知道選的什麼門,但是篤定的喊道:「改就完事兒了!」
最終選擇的門緩緩打開,大獎藏在其後!5000萬!!!
在全場的歡呼聲中,憑著我豐富的韭菜經驗,一瞬間便規劃好了理財方針:
- 5000萬的大獎,先交稅,再花1000萬成立程式猿不加班基金會
- 1000萬投P2P,保守估計15%的年化,一年光利息都有150萬進賬
- 接著,1000萬梭哈比特幣,此幣作為傳家寶代代相傳,價值不可估量
- 最後,剩下的……
話音未落,眼前一黑。
我,大獎得主,韭菜大拿,不知道被誰擠下線了……