问题描述
约瑟夫问题是个著名的问题:N个人围成一圈,第一个人从1开始报数,报M的将被杀掉,下一个人接着从1开始报。如此反复,最后剩下一个,求最后的胜利者。
分析过程
构造链表法时间复杂度过大,不做讨论。
数学解法:使用动态规划求解。这里令:f(n,m)表示在n个人里从1循环报数,报数到m的人离开,直到最后只剩下一个人,其返回值为最后一个人在这初始n个人里的索引(索引每离开一个人,就会变一次)。那么People[f(n,m)]就是这个胜利者。那么:考虑f(n-1,m)与f(n,m)是否有关系?如果有关系,那么就可以构造递推关系式,使用动态规划的方式去求解胜利者了。
因为每次离开一个人,报数会从这个人的下一个人重新开始,那么在下一轮的报数开始,索引0的位置处就是离开人的下一位。假设离开者在上一轮的位置索引是t,那么它的下一位在上一轮的索引是t+1。在这位离开者离开后,他的下一位在新一轮里的索引是0。也就是索引减少了t。那么这个t与m有什么关系?下面分情况讨论:如果m小于当前层的人数,那么t=m。如果m大于当前层的人数numbers,那么t=m%numbers。也就是说:去除离开者后,其它当前轮的幸存者们的索引会统一减去 m%numbers(将两种情况总结后的结论,对于离开者前面的人,索引还要加上numbers)。那么反推:已知离开后的每个人的索引,他们在上一轮(加入上一位离开者)后,在上一轮的索引位置是什么?推理可得:index_new=(index_old+m%numbers_new)%numbers_old=(index_old+m)%numbers_old。
基于这种情况,就可以反向推导出最后的索引。
代码
对于1,…,n的初始编号,报数m离开,代码如下:
public int getPerson(int n,int m){
int index=0; //f(1,m)的索引位置,因为只剩下一个人,索引必为0
for(int i=2;i<=n;i++){
index=(index+m)%i;
}
return index+1;//数字编号与索引的关系正好是大于1关系
}
易错点讲解
本题的目标是找到胜利者在初始数组(没有人离开时)的位置索引,并最终通过这个位置索引确定谁是胜利者,而不是胜利者的编号。可以把开始没报号的人想象成一个数组,我们的目的并不是维护一个确定不变的数,而是说这个数组大小会改变,胜利者在这个数组中的位置索引也会跟着数组大小改变而改变(增减某一个人)。因此找的规律其实是胜利者位置索引的变化规律。
这里定义了:每轮第一个报“1”的人会自动变成此次报数过程中的索引0位置。整个动态规划过程是在这个基础上取求解的。而往往题设中的数字编号:1,2,3,4…n会导致迷惑,其实把他们换成a,b,c,d…也可以,只要记住这个数组的排列顺序,最后就可以使用索引取求解。用数字编号简化了这个过程。
可以通过画图的方式取帮助理解。