菜鳥系列之C/C++經典試題(五)
求圓圈中剩下的最後一個數字
題目:n個數字(0,1,…,n-1)形成一個圓圈,從數字0開始。每次從這個圓圈中刪除第m個數字(第一個為當前數字本身。第二個為當前數字的下一個數字)。當一個數字刪除後,從被刪除數字的下一個繼續刪除第m個數字。求出在這個圓圈中剩下的最後一個數字。
本題就是著名的約瑟夫環問題。
本題的解法我們比較easy想到用鏈表,當然我們能夠自己寫一個鏈表。也能夠直接用stl庫中的list,實現代碼例如以下:
//使用標準庫 int JosephusProblem_Solution2(int n, int m) { if(n < 1 || m < 1) return -1; list<int> listInt; unsigned i; //初始化鏈表 for(i = 0; i < n; i++) listInt.push_back(i); list<int>::iterator iterCurrent = listInt.begin(); while(listInt.size() > 1) { //前進m - 1步 for(i = 0; i < m-1; i++) { if(++iterCurrent == listInt.end()) iterCurrent = listInt.begin(); } //暫時保存刪除的結點 list<int>::iterator iterDel = iterCurrent; if(++iterCurrent == listInt.end()) iterCurrent = listInt.begin(); //刪除結點 listInt.erase(iterDel); } return *iterCurrent; }
分析:上面解法的時間復雜度為(n-1)*m,即(mn)。讓我們試圖來尋找一種更好的解法。
我們用歸納法來分析。假如我們能找到第n-1個數和第n個數關系。我們就能夠一次遞推到刪除到1個數在2個數的位置。在3….n個數中的文字,最後得到解。
如今我們來找這個關系:
在這n個數字中,第一個被刪除的數字是(m-1)%n。為簡單起見記為k。
那麽刪除k之後的剩下n-1的數字為0,1,…,k-1,k+1,…,n-1,而且下一個開始計數的數字是k+1。
相當於在剩下的序列中。k+1排到最前面。從而形成序列k+1,…,n-1,0,…k-1。
k+1 -> 0
k+2 -> 1
…
n-1 -> n-k-2
0 -> n-k-1
…
k-1 -> n-2
如今我們知道了有n-1個數時last的位置。記為f(n-1,m),那麽怎樣來求得f(n,m)關於f(n-1,m)之間的關系?用X,Y來表示,例如以下:
Y X
k+1 -> 0
k+2 -> 1
…
n-1 -> n-k-2
0 -> n-k-1
…
k-1 -> n-2
y=( x+k+1) %n
k = (m-1)%n
所以y=(x+m)%n,終於關系例如以下:
0 n=1
f(n,m)={
[f(n-1,m)+m]%n n>1
所以,我們能夠用上面的是,最後剩下的數即n=1時要刪除的數。 n = 1 時, f(n, m ) = 0,由此我們知道, f(2, m)…
代碼例如以下:int JosephusProblem_Solution4(int n, int m) { if(n < 1 || m < 1) return -1; vector<int> f(n+1,0); for(unsigned i = 2; i <= n; i++) f[i] = (f[i-1] + m) % i; return f[n]; }
有錯請指出, 分享請標明出處! 謝謝
菜鳥系列之C/C++經典試題(五)