一、题目描述
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;
}
四、参考链接