题目如下:
据说著名犹太历史学家 Josephus 有过以下故事:在罗马人占领乔塔帕特后,39 个犹太人与 Josephus 及他的朋友躲到一个洞中,39 个犹太人决定宁愿死也不要被敌人抓到,于是决定了一种自杀方式,41 个人排成一个圆圈,由第 1 个人开始报数,报数到 3 的人就自杀,然后再由下一个人重新报 1,报数到 3 的人再自杀,这样依次下去,直到剩下最后一个人时,那个人可以自由选择自己的命运。这就是著名的约瑟夫问题。现在请用单向环形链表得出最终存活的人的编号。
n 表示环形链表的长度, m 表示每次报数到 m 就自杀
解题方法1(递归)
如果只求最后一个报数胜利者的话,我们可以用数学归纳法解决该问题,为了讨论方便,先把问题稍微改变一下,并不影响原意:
问题描述:n个人(编号0~(n-1)),从0开始报数,报到(m-1)的退出,剩下的人 继续从0开始报数。求胜利者的编号。
我们知道第一个人(编号一定是(m%n)-1) 出列之后,剩下的n-1个人组成了一个新的约瑟夫环(以编号为k=m%n的人开始):
k k+1 k+2 … n-2, n-1, 0, 1, 2, … k-2并且从k开始报0。
现在我们把他们的编号做一下转换:
k --> 0
k+1 --> 1
k+2 --> 2
…
…
k-2 --> n-2
k-1 --> n-1
变换后就完完全全成为了(n-1)个人报数的子问题,假如我们知道这个子问题的解: 例如x是最终的胜利者,那么根据上面这个表把这个x变回去不刚好就是n个人情况的解吗?!!变回去的公式很简单,相信大家都可以推出来:x’=(x+k)%n
令f[i]表示i个人玩游戏报m退出最后胜利者的编号,最后的结果自然是f[n]。
递推公式:
f[1]=0;
f[i]=(f[i-1]+m)%i; (i>1)
有了这个公式,我们要做的就是从n-1顺序算出f[i]的数值,最后结果是f[n]。 因为实际生活中编号总是从1开始,我们应该输出f[n]+1。
#include<stdio.h>
int LastRemaining_Solution(unsigned int n, unsigned int m)
{
if(n==0)
return -1;
if(n==1)
return 0;
else
return (LastRemaining_Solution(n-1,m)+m)%n;
}
int main()
{
printf("%d\n",LastRemaining_Solution(5,3));
//输出结果为编号3,也就是第4个人
return 0;
}
方法2(常规方法)
#include <stdio.h>
#include <stdlib.h>
//约瑟夫环
int JosephProblem(int n)
{
//创建n个标记
int *arr = (int *)calloc(n,sizeof(int));
//默认每个都是0 ,0表示还在游戏,1表示退出
int i = 0;//arr下标
int count = n;//还在游戏的人数
int tmp = 0;//报数器
while(count > 1)//有1个以上的人在游戏
{
if(arr[i] == 0)
{
tmp++;
if(tmp == 3)//退出游戏
{
arr[i] = 1;
count--;//还在游戏的人数减一
tmp = 0;//报数器还原
}
}
i = (i+1)%n;//此时使用i++;是error的 因为我们要处理环形
}
//找到还在游戏的人
for(i=0;i<n;i++)
{
if(arr[i] == 0)
break;
}
free(arr);
return i+1;
}
int main()
{
int n=JosephProblem(5);
printf("%d\n",n);
//输出结果为4,也就是第4个人
return 0;
}