剑指offer:面试题45——(孩子们的游戏)圆圈中最后剩下的数字
题目:每年六一儿童节,牛客都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此。HF作为牛客的资深元老,自然也准备了一些小游戏。其中,有个游戏是这样的:首先,让小朋友们围成一个大圈。然后,他随机指定一个数m,让编号为0的小朋友开始报数。每次喊到m-1的那个小朋友要出列唱首歌,然后可以在礼品箱中任意的挑选礼物,并且不再回到圈中,从他的下一个小朋友开始,继续0…m-1报数…这样下去…直到剩下最后一个小朋友,可以不用表演,并且拿到牛客名贵的“名侦探柯南”典藏版(名额有限哦!!_)。请你试着想下,哪个小朋友会得到这份礼品呢?(注:小朋友的编号是从0到n-1)
思路一:如果可以允许使用stl,采用list模拟一个循环链表,每次退出一个人,则erase掉这个人,最终看留下了谁。如果不允许使用循环链表,可以采用数组的方式计算
思路二:这个问题产生于约瑟夫问题,第一次人数是0~n-1,当退出了m-1人时,序列下一次从m开始算起,即m,m+1,m+2…n-1,0,1,…m-2
如果对这个序列重新映射为0~n-2,可以发现问题又被归结为上一次问题求第m个退出的人,因此是个递归问题。加入映射关系设为f,则每次退出的人为f(x) = (x-m-1)%n ,那么我们反推,假如已知这次退出的人为x,那么他在圆圈中的位置编号根据映射的逆应该为(x+m+1)%m
即:
f(n,m)= 0 n=1
[f(n-1),m]%n n>1
如果用循环实现,可以理解为,每次剩下i个人,最后是ret位置的人没有退出,根据映射,上一次它在这个圆圈中被重新编号的位置为
ret=(ret+m)%i ,总共进行了n轮游戏,因此在n个人进行游戏,即第一轮游戏时,它的位置为i从n循环得到的ret的值
/*
//循环队列法
if(n<1||m<1) return -1;
int i=0;
list<int> numbers;
for(i=0;i<n;i++)
{
numbers.push_back(i);
}
list<int>::iterator ite=numbers.begin();
while(numbers.size()>1)
{
for(int i=0;i<m-1;i++) //遍历找到第m-2个位置。因为第m-1个人出列下标实际上是m-2
{
ite++;
if(ite==numbers.end())
ite=numbers.begin();
}
list<int>::iterator next=++ite; //next指向下一个,先++再赋值,保存下一节点
if(next==numbers.end())
next=numbers.begin();
--ite; //指回要删除的节点
numbers.erase(ite); //调用erase删除结点
ite=next;
}
return *ite;
*/
if(n<1||m<1)return -1;
int ret=0;
int i;
for(i=1;i<=n;i++) //最后剩下一个人,总共n个人进行游戏
ret=(ret+m)%i;
return ret;