题目:
0,1,...,n-1这n个数排成一个圆圈,从数字0开始每次从这个圆圈里删除第m个数字。求出这个圆圈里剩下的最后一个数字。
例如,0、1、2、3、4这5个数字组成一个圆圈,从数字0开始每次删词第3个数字,则删除的前四个数字一次是2、0、4、1,因此最后剩下的数字是3.
思路:
方法1:用环形链表模拟圆圈,链表可以用c++的STL库中的list来表示,删除链表其中一个结点之后,链表还是连续的。
方法2:定义f(n,m),表示在n个数字0,1,...,n-1中每次删除第m个数字最后剩下的数字。
先给出关系公式:
可以这样理解:从序列f(n):0~n-1中删除第m个数后,剩下为序列m,m+1,..,n-1,0,1,...,m-2,即序列f(n-1)+m.因此f(n,m) =[f(n-1,m)+m]%n,是采用的映射关系来递推的。
即要得到n个数字的序列中最后剩下的数字,只需要得到n-1个数字的序列中最后剩下的数字,并以此类推。当n=1时,即序列中开始只有一个数字0,那么最后剩下的数字就是0。
代码:
#include <iostream>
#include <list>
using namespace std;
//题目:0,1,……,n-1这n个数字排成一个圆圈,从数字0开始每次从这个圆圈里删除第m个数字。
//求出这个圆圈里剩下的最后一个数字。
//例如,0、1、2、3、4这5个数字组成一个圆圈,从0开始每次删除第3个数字(删完之后到其后的数字),则删除的前4个数字依次是2、0、4、1
//因此最后剩下的数字是3.
//本题就是有名的约瑟夫环问题。两种方法:1、环形链表模拟 2、找出规律,直接计算
//1、用环形链表模拟圆圈
int LastRemaining(unsigned n , unsigned m)
{
if(n < 1 || m < 1)
return -1;
unsigned int i = 0;
list<int> numbers;
for(i = 0 ; i < n ; i++)
{
numbers.push_back(i);
}
list<int>::iterator current = numbers.begin();
while(numbers.size() > 1)
{
//找打第m个位置
for(int i = 1 ; i < m ; i++)
{
current++;
if(current == numbers.end())
current = numbers.begin();
}
//cout<<"删除的数是:"<< *current<<endl;
current = numbers.erase(current); //直接删除current,返回current的后一个迭代器(有可能是end)
if(current == numbers.end())
{
current = numbers.begin();
}
}
return *current;
}
int LastRemaining2(unsigned n , unsigned m)
{
if(n < 1 || m < 1)
return -1;
int last = 0;
for(int i = 2 ; i <= n ; i++)
{
last = (last + m) % i;//i就是上面递推公式的n,i由2一直增长到n
}
return last;
}
int main()
{
cout<<"begin"<<endl;
int number2 = LastRemaining2(1000000 , 3);
cout<<number2<<endl;
int number = LastRemaining(1000000 , 3);
cout<<number<<endl;
}