劍指offer——約瑟夫環問題

前幾天華爲機試做到類似題,當時並不知道,用數組強行模擬然後掛掉

看來還是要多讀書,閉門造車是不行的!

用鏈表或者數組模擬是不行的!得用公式求解:

f(N,M)=(f(N−1,M)+M)%N

這個公式咋來的,我看到另一篇很好的博客,直接拿來了:https://blog.csdn.net/u011500062/article/details/72855826

約瑟夫環是一個經典的數學問題,我們不難發現這樣的依次報數,似乎有規律可循。爲了方便導出遞推式,我們重新定義一下題目。
問題: N個人編號爲1,2,……,N,依次報數,每報到M時,殺掉那個人,求最後勝利者的編號。

這邊我們先把結論拋出了。之後帶領大家一步一步的理解這個公式是什麼來的。
遞推公式:
f(N,M)=(f(N−1,M)+M)%N

f(N,M)表示,N個人報數,每報到M時殺掉那個人,最終勝利者的編號f(N−1,M)表示,N-1個人報數,每報到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
  • f(2,3)=(f(1,3)+3)%2=3%2=1:在有2個人的時候,勝利者的下標位置爲1
  • f(3,3)=(f(2,3)+3)%3=4%3=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
  • 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

問題3:現在改爲人數改爲N,報到M時,把那個人殺掉,那麼數組是怎麼移動的?
答:每殺掉一個人,下一個人成爲頭,相當於把數組向前移動M位。若已知N-1個人時,勝利者的下標位置位f(N−1,M)
f(N−1,M),則N個人的時候,就是往後移動M爲,(因爲有可能數組越界,超過的部分會被接到頭上,所以還要模N),既f(N,M)=(f(N−1,M)+M)%n

注:理解這個遞推式的核心在於關注勝利者的下標位置是怎麼變的。每殺掉一個人,其實就是把這個數組向前移動了M位。然後逆過來,就可以得到這個遞推式。

class Solution {
public:
    int lastRemaining(int n, int m) {
        int last = 0;//如果是隻有一個人的情況,那麼下標爲0
        for(int i=2;i<=n;i++)
            last = (last+m)%i;//i代表人數,自底向上模擬
        return last;   
    }
};

 

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