社區舉辦“殺戮遊戲”,你是倖存的那個嗎?

“殺戮遊戲”通告

X社區要舉辦“殺戮遊戲”,你會是倖存的那個人嗎?
遊戲規則如下:
在這裏插入圖片描述

所有人(本例子中12個人)排成一個圓圈,由第一個人開始,向下殺死相鄰的人,直到剩下一個人,遊戲結束。
獲勝條件:你爲了在這場“殺戮遊戲”中存活下來,需要選擇一個幸運號碼,本例子中爲9。

你的直覺是什麼

大多數人應該和我一樣,第一想法是構造一個鏈表數據結構,把所有人放到這個鏈表裏面。
在這裏插入圖片描述

只要你懂得數據結構,這個解法非常直觀。邏輯上就是每次殺死一個人,把代表這個人的節點刪除,類似下面這樣。
在這裏插入圖片描述

遞歸思想是一個好東西

也許你從另外一個思路入手,利用遞歸思想來解決這個問題。
遞歸樹如下:
在這裏插入圖片描述

我們觀察如下情況:
退出條件爲
j(1,k) = 0 物理含義爲:如果只有一個人參加遊戲,則這個人必然存活,返回此時他的編號(假設由0開始編號)

遞歸之間的關係轉移爲:
j(n,k) =( j(n-1, k) + k ) % n

關係式中的 j(n-1, k) + k 物理意義:
 j(n-1, k) 爲n-1規模存活下來的人,
 此時這個人的編號+k就是n規模存活下來的人的編號(這個編號有可能超出n),
 所以,考慮到所有的人都在一個圈上,需要對n取餘,返回n範圍以內的編號。

有了思路,代碼真的不重要,但還是寫一下,便於大家理解
代碼如下:

def kill_game(n, k):
    if n == 1:
        return 0
    return (kill_game(n-1,k) + k ) % n
n = 12
k = 2
print("你的幸運號碼是:{}".format(kill_game(n, k)+1))
#最後這裏+1的,是因爲最開始示例中使用的編號是從1開始。
#如果你的編號是從0開始,則可以忽略這個1

輸出

你的幸運號碼是:9

這種方式的好處就是實現起來,代碼非常簡單。

你還是被殺死了

社區舉辦這場“殺戮遊戲”是參考歷史上赫赫有名的約瑟夫環。

約瑟夫環(Josephus)問題是由古羅馬的史學家約瑟夫(Josephus)提出的,他參加並記來錄了公元66—70年猶太人反抗羅馬的起義。約瑟夫作爲一個將軍,設法守住了裘達伯特城達47天之久,在城市淪陷之後,他和源40名死硬的將士在附近的一個洞穴中避難。在那裏,這些叛亂者表決說“要投降毋寧死”。於是,約瑟夫建議每個人輪流殺死他旁邊的人,而這百個順序是由抽籤決定的。約瑟夫有預謀地抓到了最後一簽,並且,作爲度洞穴中的兩個倖存者之一,他說服了他原先的犧牲品一起投降了羅馬。
網絡上還有該問題的其它變種。

爲了還原當時的歷史條件,X社區決定不允許使用電腦編程這樣的輔助設備,只能憑腦力。
用算法的語言描述就是你用O(N)的時間複雜度來解決這個問題,會被TLE(Time Limit Exceeded)。

更快的算法。

我們先來縮小規模,來觀察這個問題,看有沒有規律可以尋找。
規模縮小到8
在這裏插入圖片描述
在這裏插入圖片描述

第一個規律

如果n滿足2的冪數倍,那最後存活下來的人一定是最開始的那個人:本例子中爲1。

在這裏插入圖片描述

第二個規律

如果n不是2的整數倍,我們可以把n表示爲n=2x+ln=2^x+l 其中x是n範圍內2的最大次冪。
比如13 我們就可以表示爲13=23+513 = 2^3 + 5 也就是 13=8+513 = 8 + 5。此時如果x爲4,則242^4 > 13,所以x只能爲3.
在這裏插入圖片描述
此時遊戲人數是13,我們知道8是可以被2整除的,所以可以自然的想到,先幹掉5個人,剩下的8個人開始的位置 i 就是最後存活下來的人。
如下圖:
在這裏插入圖片描述
在這裏插入圖片描述
此時i的值爲11,剩下了8個人,8==232^3.

最後的存活者就是11,你可以用剛纔的遞歸代碼去驗證。

n =13
k = 2
print("你的幸運號碼是:{}".format(kill_game(n, k)+1))

輸出:

你的幸運號碼是:11

於是我們可以觀察到這個規律這種情況下的

倖存號碼=2l+12*l+1
本例中=> ( 2 * 5 +1)

最終的公式

存活下來的人可以用如下關係表示:
n=2x+ln=2^x + l
13=23+513=2^3+5 可得 x=3,l=5{x=3, l=5}
存活下來的人(i)
i=2l+1i = 2*l+1 可得 i=11i=11
在這裏插入圖片描述

你現在能活下來嗎?

現在你可以活下來了嗎?
現在你已經被拉到了遊戲現場,一共有41個人,已經開始選號碼了!
你要選擇多少?
把答案寫在評論區吧!

滿足你的強迫症

雖然這個問題,只要n不是特別誇張,對於已經熟練對65536求log的你應該已經可以心算了。
可我們畢竟是開發人員,總感覺不寫段代碼就哪裏不對勁,爲了滿足這種強迫症,奉上代碼.

import math
def kill_game(n):
    x = math.floor(math.log(n, 2))
    l = n - math.pow(2, x)
    luck_num = int(2*l+1)
    return luck_num
    
n = 13

print("你的幸運號碼是:{}".format(kill_game(n, k)))

輸出:

你的幸運號碼是:11

我最喜歡的殺戮遊戲

這場“殺戮遊戲”是我非常喜歡在電話面試中用來考覈候選者的一道面試題。
考察候選者對數據結構中的鏈表與遞歸思想進行考覈,只要有了思路,編碼實現一般都不會有什麼大問題。
最後會和候選人探討,有沒有更快的實現方式?也就是本文中重點介紹的方法。當然候選人在面試情況下如果第一次接觸這個問題,我並不會要求他給出正確的答案,只要他有正確的觀察思路,我就會判他通關。

算法與真實的世界

在現實生活中,爲什麼很多事情,同樣的人做會有不同的效果。
就在於你對這個問題的認識深度,能不能用算法,用模型去思考。每個人的時間都是有限的,如何用有限的時間去高效的解決問題,這是一個技術活!!
算法不僅僅是爲了編程,他與我們的真實世界息息相關。
算法是能給你帶來財富,節省生命的寶貴工具。

恭喜你在遊戲中獲勝了


在這裏插入圖片描述


我的其他文章
最火的瓜,得用動態規劃來吃
A姓女友,B姓女友,渣男與最長公共子串(有視頻)
就這一次幹翻動態規劃 - Longest Common Subsequence(有視頻)

參考資料
感謝Numberphile 製作的視頻素材

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章