1. 程式人生 > >【Algo】約瑟夫問題(Josephus problem / Josephus permutation)

【Algo】約瑟夫問題(Josephus problem / Josephus permutation)

NN 個人圍一圈, 選一個人, 從 1 開始計數, 計到第 kk 個人出局, 剩下的人組成新環, 再次從 1 開始計數, 計到 kk 的人出局. 如此反覆, 直到剩下最後一個人, 作為勝利者. 問, 在遊戲開始時, 排在第幾號位置, 可以保證自己是最後的勝利者?

舉一個特例理解題目, 設 N=6,k=5N = 6, k = 5, 則這一圈人是

1 2 3 4 5 6 1

出局的順序依次是 5 -> 4 -> 6 -> 2-> 3, 所以開局時候排在第 11 位是勝利者.

程式設計求解的直接思路: 用一個 迴圈連結串列 儲存這 NN

個人, data 裡存一個 bool 值, true 表示還在場上, false 表示出局. 然後迴圈遍歷 N1N - 1次, 最後剩下的人就是勝利者. But, 時間複雜度是 N×kN \times k, 任何一個數非常大的話, 這個複雜度都是不可接受的.

優化, 重新分析問題, 試圖找到其中的隱含規律. 計算機語言問題描述:nn 個人(編號0(n10\to(n-1)),從00開始報數,報到m1(m-1)的退出,剩下的人繼續從 00 開始報數。求勝利者的編號。

我們可以確定, 第一個出列的人編號肯定是 (m1)mod  n

(m-1) \mod n, 那麼剩下的 n1n-1 個人組成新的 約瑟夫環, 新環肯定以 (k=mmod  nk = m \mod n) 開始, 新環的排序肯定是 k,k+1,k+2,...,n2,n1,0,1,2,...,k2k, k+1, k+2, ... , n-2, n-1, 0, 1, 2, ... , k - 2. 把這個新環的序號調整一下, 看做一個新的約瑟夫環

  • k0k \to 0
  • k+11k+1 \to 1
  • k+22k+2 \to 2
  • ......
  • k2n2k-2 \to n-2

也就是說變成了 n1n-1 個人報數的子問題. 假設我們知道對於這個 n1n-1個人的子問題, 第 xx 個人是最終的勝利者, 那麼我們再把這個 xx 塞回去就是剛好 nn個人情況下的解了. 變回去的公式就是 x=(x+k)mod  nx' = (x+k) \mod n. 因此, 我們知道了 n1n-1 下的解就可以求出 nn 的解, 典型的遞迴問題.

const int n = 68; // total number
const int m = 3; // kill the m_th person
int f = 0; // safe position
int main()
{
    for (int i = 1; i <= n; i++) 
    	f = (f + m) % i;
    cout << f + 1 << endl; // counts from 1
}

時間複雜度 O(n)O(n), 空間複雜度 O(1)O(1), 好很多了.

Ref