面试题45:圆圈中最后剩下的数字
题目:0,1,...,n-1这n个数字排成一个圆圈,从数字0开始每次从这个圆圈里删除第m个数字。求出这个圆圈里剩下的最后一个数字。
例如,0、1、2、3、4这5个数字组成一个圆圈,从数字0开始每次删除第3个数字,则删除的前四个数字一次是2、0、4、1,最后剩下的数字为3.
本题是有名的约瑟夫环问题。
解法一:经典的解法,用环形链表模拟圆圈
如果面试官没有特殊要求,可以用模板库中的list来模拟一个环形链表,而list本身不是一个环形结构,因此每当迭代器扫描到链表末尾的时候,
要记得把迭代器移到链表的头部。
1 int lastRemaining(unsigned int n, unsigned int m) 2 { 3 if (n < 1 || m < 1) 4 return -1; 5 list <int>numbers; 6 for (int i = 0; i < n; ++i) 7 numbers.push_back(i); 8 list<int>::iterator current = numbers.begin(); 9 while (numbers.size() > 1) 10 { 11 for (int j = 1; j < m; ++j) 12 { 13 ++current; 14 if (current == numbers.end()) 15 current = numbers.begin(); 16 } 17 current = numbers.erase(current); 18 if (current == numbers.end()) 19 current = numbers.begin(); 20 } 21 return *current; 22 }
这种思路的时间复杂度为O(MN),空间复杂度为O(N)。
解法二:创新的解法
首先定义一个关于n和m的方程f(n,m),表示每次在n个数字0,1,...,n-1中每次删除第m个数字后最后剩下的数字。经过分析(过程参考《剑指Offer》)得到:
n = 1时,f(n,m) = 0;
n > 1时,f(n,m) = [f(n-1,m)+m]%n;
1 int lastRemaining(unsigned int n, unsigned int m) 2 { 3 if (n < 1 || m < 1) 4 return -1; 5 int last = 0; 6 for (int i = 2; i <= n; ++i) 7 last = (last + m)%n; 8 return last; 9 }