"约瑟夫环"问题
约瑟夫环:在罗马人占领乔塔帕特后,39 个犹太人与 Josephus 及他的朋友躲到一个洞中,39 个犹太人决定宁愿死也不要被敌人抓到,于是决定了一种自杀方式,41 个人排成一个圆圈,由第 1 个人开始报数,报数到 3 的人就自杀,然后再由下一个人重新报 1,报数到 3 的人再自杀,这样依次下去,直到剩下最后一个人时,那个人可以自由选择自己的命运。这就是著名的约瑟夫问题。现在请用单向环形链表得出最终存活的人的编号。
首先设置一个长度为n的数组,默认初始化为false,若当前数组中的元素的值为false,说明该元素还未被移除,若当前数组中的元素为true,先将sum++,此时就需要对当前用于计数的sum与m值进行比较,如果sum==m,说明当前位置上的值需要被设置为true(表示该元素已被移除),并将sum重新设置为0,淘汰人数pass+1,若sum!=m,说明此时的sum还不到m,因此将i++,继续判断即可。若当前元素是false,说明当前元素已被移除,直接i++找后一个即可,循环的退出条件是当pass淘汰的人数=总人数(pass<n),此时最终返回的i即为最后一个人所处的编号。
由于在每次sum++后的值与m进行判断,如果相同会将当前i位置上的元素设置为true,再将i++,因此当前i位置上对应最终返回的其实是i+1,这也就将数组的下标与编号(从0开始)一一对应起来。当最后一个元素返回时,其返回的i并不是下标,而是下标+1.也就刚好是编号的值。
/**
* Created by xiaoaxiao on 2019/11/27
* Description: n个人,报到m出去,最后剩下的那个人的编号(从1开始)
* 时间复杂度:O(n²) 空间复杂度:O(n)
*/
public class JosephRing {
public static int getResult(int n, int m) {
// 设置一个数组表示这些人是否存活
// 先假设这n个人都活着(false)
boolean[] people = new boolean[n];
// 设置当前的编号(1-m)
int sum = 0;
// 已经被淘汰了多少人
// 通过这个已被淘汰的人和总人数的比值,进行循环的退出
int pass = 0;
// 定义一个i表示当前数组的下标
int i = 0;
while (pass < n) {
if (i == n) { // 需要对该数组进行循环遍历,因此当i==n时,将i设置为0
i = 0;
}
if (!people[i]) { // 若当前位置上的人还没有被淘汰
sum++;
// 若此时sum==m,说明当前这个位置上报的数就是m
// 先++再比较,因为sum是从0开始计数的
if (sum == m) {
// 淘汰的人+1
pass++;
// 该位置被设置为true,意味着该位置已被淘汰
people[i] = true;
// 将sum重新设置为0,进行下一轮的约瑟夫过程
sum = 0;
}
// 无论当前位置上的人是否被淘汰,i都需要向后走一步
// 比较巧妙:如果是最后一次pass==n时,此时的i再+1,刚好对应了实际的编号(从1开始)
i++;
} else { // 若当前位置上的人已经被淘汰了,直接i++往后走就行
i++;
}
}
return i;
}
}