《剑指offer》约瑟夫环问题
2016-05-05 14:58
239 查看
一、题目描述
输入:
每组数据一行,包含
输出:
输出能保留到最后的那个数字。
样例输入:
样例输出:
二、题目分析
经典的约瑟夫环问题,最简单粗暴的方法就是用数组或者环形链表模拟整个删除元素的过程,这里使用标准库的
另一种网上讨论很多的方法是使用数学推导的方法求出递推式,从而实现
(1)对于n个数组成的序列,第一次被删除的数为:
(2)假设第二轮删除时,初始数字为
也就是说,
(3)类似的,现在考虑第二个被删除的数:(m - 1) % (n - 1)。
(4)假设第三轮的开始数字为p,那么这n - 2个数构成的约瑟夫环为
也就是说,
由以上内容得知,要求得
有了递推公式,实现就非常简单了,给出循环的两种实现方式。再次表明用标准库的便捷性。
三、示例代码
四、参考链接
http://blog.csdn.net/wuzhekai1985/article/details/6628491
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; }
四、参考链接
http://blog.csdn.net/wuzhekai1985/article/details/6628491
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- 关于指针的一些事情
- c++ primer 第五版 笔记前言
- share_ptr的几个注意点
- Lua中调用C++函数示例
- Lua教程(一):在C++中嵌入Lua脚本
- Lua教程(二):C++和Lua相互传递数据示例
- C#中Ilist与list的区别小结
- C#中IList<T>与List<T>的区别深入解析
- C++联合体转换成C#结构的实现方法
- C#对list列表进行随机排序的方法
- C++高级程序员成长之路
- C++编写简单的打靶游戏
- C++ 自定义控件的移植问题
- C++变位词问题分析
- C/C++数据对齐详细解析
- C++基于栈实现铁轨问题
- C++中引用的使用总结
- 使用Lua来扩展C++程序的方法
- C++中调用Lua函数实例