剑指offer:约瑟夫环的问题
什么是約瑟夫環問題?
約瑟夫問題是個著名的問題:N個人圍成一圈,第一個人從1開始報數,報M的將被殺掉,下一個人接著從1開始報。如此反復,最后剩下一個,求最后的勝利者。
例如只有三個人,把他們叫做A、B、C,他們圍成一圈,從A開始報數,假設報2的人被殺掉。
- 首先A開始報數,他報1。僥幸逃過一劫。
- 然后輪到B報數,他報2。非常慘,他被殺了
- C接著從1開始報數
- 接著輪到A報數,他報2。也被殺死了。
- 最終勝利者是C
解決約瑟夫環問題,我們首先來分析一下!
1)普通解法
剛學數據結構的時候,我們可能用鏈表的方法去模擬這個過程,N個人看作是N個鏈表節點,節點1指向節點2,節點2指向節點3,……,節點N-1指向節點N,節點N指向節點1,這樣就形成了一個環。然后從節點1開始1、2、3……往下報數,每報到M,就把那個節點從環上刪除。下一個節點接著從1開始報數。最終鏈表僅剩一個節點。它就是最終的勝利者。
缺點:
要模擬整個游戲過程,時間復雜度高達O(nm),當n,m非常大(例如上百萬,上千萬)的時候,幾乎是沒有辦法在短時間內出結果的。
2)公式法
約瑟夫環是一個經典的數學問題,我們不難發現這樣的依次報數,似乎有規律可循。為了方便導出遞推式,我們重新定義一下題目。
問題: N個人編號為1,2,……,N,依次報數,每報到M時,殺掉那個人,求最后勝利者的編號。
這邊我們先把結論拋出了。之后帶領大家一步一步的理解這個公式是什么來的。
遞推公式:
?
f(N,M)=(f(N?1,M)+M)%N?f(N,M)=(f(N?1,M)+M)%N
?
- f(N,M)?f(N,M) 表示,N個人報數,每報到M時殺掉那個人,最終勝利者的編號
- f(N?1,M)?f(N?1,M) 表示,N-1個人報數,每報到M時殺掉那個人,最終勝利者的編號
下面我們不用字母表示每一個人,而用數字。
1、2、3、4、5、6、7、8、9、10、11?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)?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?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 。不過有可能數組會越界,所以最后模上當前人數的個數,f(11,3)=(f(10,3)+3)%11?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?f(N,M)=(f(N?1,M)+M)%n
注:理解這個遞推式的核心在于關注勝利者的下標位置是怎么變的。每殺掉一個人,其實就是把這個數組向前移動了M位。然后逆過來,就可以得到這個遞推式。
因為求出的結果是數組中的下標,最終的編號還要加1。
劍指offer題目:每年六一兒童節,牛客都會準備一些小禮物去看望孤兒院的小朋友,今年亦是如此。HF作為牛客的資深元老,自然也準備了一些小游戲。其中,有個游戲是這樣的:首先,讓小朋友們圍成一個大圈。然后,他隨機指定一個數m,讓編號為0的小朋友開始報數。每次喊到m-1的那個小朋友要出列唱首歌,然后可以在禮品箱中任意的挑選禮物,并且不再回到圈中,從他的下一個小朋友開始,繼續0...m-1報數....這樣下去....直到剩下最后一個小朋友,可以不用表演,并且拿到牛客名貴的“名偵探柯南”典藏版(名額有限哦!!^_^)。請你試著想下,哪個小朋友會得到這份禮品呢? (注:小朋友的編號是從0到n-1)
拿到這一題,對比上面的兩種思路:
1)使用數組模仿一個環。
2)使用公式遞歸求解。
首先看第一種思路:
class Solution { public:int LastRemaining_Solution(unsigned int n, unsigned int m){/*每輪被select的數被設置成-1;超出數組范圍的,回到數組起點,模仿一個環;每次再次走到之前select的數的時候,就continue;*///if(n<1||m<1) return -1;int* array = new int[n];int i = -1, step = 0, count = n;while(count>0){i++;if(i>=n) i = 0;//模仿一個環if(array[i]==-1) continue;//每次再走到之前select的數時,就continue;step++;if(step == m){array[i] = -1; //每輪被select的數被設置成-1;step = 0;count--;}}return i; //此題小朋友的編號是從0到n-1} };再看第二種思路:
class Solution { public:int LastRemaining_Solution(unsigned int n, unsigned int m){if(n==0)return -1;if(n==1)return 0;elsereturn (LastRemaining_Solution(n-1,m)+m)%n;} };?
總結
以上是生活随笔為你收集整理的剑指offer:约瑟夫环的问题的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 14.线程安全?线程不安全?可重入函数?
- 下一篇: map:erase删除元素之后迭代器失效