社區舉辦“殺戮遊戲”,你能活下來嗎?
“殺戮遊戲”通告
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表示爲 其中x是n範圍內2的最大次冪。
比如13 我們就可以表示爲 也就是 。此時如果x爲4,則 > 13,所以x只能爲3.
此時遊戲人數是13,我們知道8是可以被2整除的,所以可以自然的想到,先幹掉5個人,剩下的8個人開始的位置 i 就是最後存活下來的人。
如下圖:
此時i的值爲11,剩下了8個人,8==.
最後的存活者就是11,你可以用剛纔的遞歸代碼去驗證。
n =13
k = 2
print("你的幸運號碼是:{}".format(kill_game(n, k)+1))
輸出:
你的幸運號碼是:11
於是我們可以觀察到這個規律這種情況下的
倖存號碼=
本例中=> ( 2 * 5 +1)
最終的公式
存活下來的人可以用如下關係表示:
可得
存活下來的人(i)
可得
你現在能活下來嗎?
現在你可以活下來了嗎?
現在你已經被拉到了遊戲現場,一共有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 製作的視頻素材