劍指offer---約瑟夫環問題
參考文獻:
換個角度舉例解決約瑟夫環
約瑟夫環——公式法(遞推公式)
在刷leetcode的劍指offer時,做到這道關於約瑟夫環的題,首先自己是一臉懵逼的。這是啥玩意,看不懂啊。後來i仔細思考之後,想到用佇列來解這道題,根據佇列先進先出的特性這樣就能迴圈解決這個問題。嗯,完美。說幹就幹。。。
但是出現了一個尷尬的問題----現在我還不太會用佇列。於是只能看看大佬的題解了,裡面有用連結串列的有用佇列的,看的我腦瓜子嗡嗡的,WTF.。但是,突然看到一個數學解法。一看,哎呦,不錯哦。簡單明瞭。但是,又但是了。因為確實看了很多遍,直到寫這篇部落格的時候還是有點迷糊。所以就打算記錄一下這些點,以後再遇見能夠好好研究研究。如果各位有什麼好的見解以及本文存在的不足之處。歡迎各位指正批評。話不多說,看看關於大佬的思路。
主要是看了這兩篇文章:首先是這一篇:
約瑟夫環——公式法(遞推公式)
這篇我覺得講的很好,至少我這個小白算是看懂了,
文中首先提到了什麼是約瑟夫問題:然後給出了兩種解決方案:
- 普通解法:運用連結串列。
- 公式法:
公式法:
約瑟夫環是一個經典的數學問題,我們不難發現這樣的依次報數,似乎有規律可循。為了方便匯出遞推式,我們重新定義一下題目。
問題:N個人編號為1,2,……,N,依次報數,每報到M時,殺掉那個人,求最後勝利者的編號。
這邊我們先把結論丟擲了。之後帶領大家一步一步的理解這個公式是什麼來的。
遞推公式:
用不同數字表示每個人:
1、2、3、4、5、6、7、8、9、10、11
表示11個人。排成一排假設每報道3的人被殺掉。
- 剛開始時,頭一個人編號是1,從他開始報數,第一輪被殺掉的是編號3的人。
- 編號4的人從1開始重新報數,這時候我們可以認為編號4這個人是隊伍的頭。第二輪被殺掉的是編號6的人。
- 編號7的人開始重新報數,這時候我們可以認為編號7這個人是隊伍的頭。第三輪被殺掉的是編號9的人。
- ……
- 第九輪時,編號2的人開始重新報數,這時候我們可以認為編號2這個人是隊伍的頭。這輪被殺掉的是編號8的人。
- 下一個人還是編號為2的人,他從1開始報數,不幸的是他在這輪被殺掉了。
- 最後的勝利者是編號為7的人。
下圖表示這一過程(先忽視綠色的一行)
圖中綠色部分表示的是這些陣列成的數的下標:
上邊得到的公式描述的是:倖存者在這一輪的下標位置
- f(1,3):只有1個人了,那個人就是獲勝者,他的下標位置是0
- f(2,3)=(f(1,3)+3)%2=3%2=1:在有2個人的時候,勝利者的下標位置為1
- f(3,3)=(f(2,3)+3)%3=4%3=1:在有3個人的時候,勝利者的下標位置為1
- f(4,3)=(f(3,3)+3)%4=4%4=0:在有4個人的時候,勝利者的下標位置為0
- ……
- f(11,3)=6
上面這個例子驗證了這個遞推公式的確可以計算出勝利者的下標,下面講解怎麼推到這個公式。
**問題1:**假設我們已經知道11個人時,勝利者的下標位置為6。那下一輪10個人時,勝利者的下標位置為多少?
**答:**其實吧,第一輪刪掉編號為3的人後,之後的人都往前面移動了3位,勝利這也往前移動了3位,所以他的下標位置由6變成3。
**問題2:**假設我們已經知道10個人時,勝利者的下標位置為3。那下一輪11個人時,勝利者的下標位置為多少?
**答:**這可以看錯是上一個問題的逆過程,大家都往後移動3位,所以f(11,3)=f(10,3)+3。不過有可能陣列會越界,所以最後模上當前人數的個數,f(11,3)=(f(10,3)+3)%11
關於越界的解釋如圖中所示;圖中講的就是如何消除陣列越界的問題。相信大家都看得懂:
因此我們可以推出遞推公式f(8,3) = [f(7, 3) + 3] %8。
進行推廣泛化,即f(n,m) = [f(n-1, m) + m]%m。
**注:**理解這個遞推式的核心在於關注勝利者的下標位置是怎麼變的。每殺掉一個人,其實就是把這個陣列向前移動了M位。然後逆過來,就可以得到這個遞推式。
文中最後說求出的結果是陣列的下標,最終編號還要加1.但是我用C語言編寫的結果不用加1直接返回結果也可以。
1 int lastRemaining(int n, int m){ 2 int pos=0; 3 for(int i=2;i<=n;i++) 4 { 5 pos=(pos+m)%i; 6 } 7 return pos; 8 }