1. 程式人生 > 實用技巧 >劍指offer---約瑟夫環問題

劍指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 }