一、題目描述
0, 1, ..., n - 1
這n
個數字排成一個圓圈,從數字0
開始每次從這個圓圈裏刪除第m
個數字。求出這個圓圈裏剩下的最後一個數字。
輸入:
每組數據一行,包含2
個整數n
和m
,分別表示0
到 n - 1
的序列和指定刪除的第m
個數字。
輸出:
輸出能保留到最後的那個數字。
樣例輸入:
5 3
樣例輸出:
3
二、題目分析
經典的約瑟夫環問題,最簡單粗暴的方法就是用數組或者環形鏈表模擬整個刪除元素的過程,這裏使用標準庫的list
實現了環形鏈表,在遍歷時需注意,當迭代器遍歷到鏈表的結尾時,需將其重新指向鏈表的頭部。
另一種網上討論很多的方法是使用數學推導的方法求出遞推式,從而實現O(n)
的時間複雜度和O(1)
的空間複雜度。但這一推導過程比較困難,網上的講解有很多,這裏引用別人的推導步驟:
(1)對於n個數組成的序列,第一次被刪除的數爲:(m - 1) % n
。
(2)假設第二輪刪除時,初始數字爲m % n
。令k = m % n
,則對於剩下的n - 1
個數構成的約瑟夫環爲:k, k + 1, k + 2, k +3, .....,k - 3, k - 2
。做一個映射如下:
k ------> 0
k+1 ------> 1
k+2 ------> 2
...
...
k-2 ------> n-2
也就是說,n - 1
個數中的一個數k
,對應n
個數時的下標爲0
。因此,設n - 1
個序列最終留到最後的數爲x
,利用映射關係逆推,可得出n
個數時,留到最後的數爲:(x + k) % n
。則有:
(x + k) % n
= (x + (m % n)) % n
= (x % n + (m % n) % n) % n
= (x % n + m % n) % n
= (x + m) % n
(3)類似的,現在考慮第二個被刪除的數:(m - 1) % (n - 1)。
(4)假設第三輪的開始數字爲p,那麼這n - 2個數構成的約瑟夫環爲p, p + 1, p + 2,..., p - 3, p - 2
。同樣得到如下映射:
p ------> 0
p+1 ------> 1
p+2 ------> 2
...
...
p-2 ------> n-3
也就是說,n - 2
個數中的一個數p
,對應n - 1
個數時的下標爲0
。設n - 2
個序列最終留到最後的數爲y
,利用映射關係逆推,可得出n - 1
個數時,留到最後的數爲:(y + p) % (n - 1)
,其中p
等於m % (n - 1)
。代入可得:(y + m) % (n - 1)
。
由以上內容得知,要求得n
個數的序列最後留下的值,可通過n - 1
個數的解來求得。遞推下去,當只有一個人時,最後一個數字是0
。綜上所述,得到以下遞推式:
f[1] = 0; f[i] = (f[i -1] + m) % i;
有了遞推公式,實現就非常簡單了,給出循環的兩種實現方式。再次表明用標準庫的便捷性。
三、示例代碼
#include <iostream>
#include <list>
using namespace std;
// The first method
// Link List of Josephus
int Josephus1(int n, int m)
{
if (n < 1 || m < 1) return -1;
list<int> circle;
for (int i = 0; i < n; ++i)
circle.push_back(i);
list<int>::iterator it = circle.begin();
while (circle.size() > 1)
{
for (int j = 0; j < m - 1; ++j)
if (++it == circle.end()) it = circle.begin();
list<int>::iterator del = it;
if (++it == circle.end()) it = circle.begin();
circle.erase(del);
}
return *it;
}
// The second method
int Josephus2(int n, int m)
{
if (n < 1 || m < 1) return -1;
int last = 0;
for (int i = 2; i <= n; ++i)
last = (last + m) % i;
return last;
}
int main()
{
int n = 0, m = 0;
cin >> n >> m;
int result1 = Josephus1(n, m);
int result2 = Josephus2(n, m);
cout << "The first method, the last member is No: " << result1 << endl;
cout << "The second method, the last member is No: " << result2 << endl;
system("pause");
return 0;
}
四、參考鏈接