n个人,报数到m的出局; 可以理解为n*m的数组,每个报数为m的倍数的人出局。 接下来就是找到每次出局一个人后后续人员的排序规律。 假设n为5,m为3;
如下图所示:
- 出局第一个人后,1和2移动到6和7的位置;
- 出局第二个人后,4和5移动到8和9的位置上;
- 出局第三个人后,2和4移动到10和11的位置;
- 出局第四个人后,4移动到13的位置,一直移动到15位置,作为最后一个出局点。
public static int ysf(long n, long m, int s) {
// s倒数第几个死的人
long last = m * (n - s + 1);
// k为存活人数
int k = s;
// step为步长,即相邻死亡人的间隔
long step = m - 1;
while (last > n) {
// k 大于等于m的时候往前推,会经过自杀点,排除掉自杀点
if (k >= m) {
// 往前推k个间隔需要经过几个自杀点,排除掉这些自杀点移动。
long count = k / step;
// 经过几个自杀点后,往前推几个位置,表示当前位置多几个存活的人。
k += count;
last = last - k;
// last变成上一次移动之前的位置。
if (last <= m * (n - k)) {
last--;
k++;
}
} else {
// 每次往前推k个位置
last = last - k;
if (last <= m * (n - k)) {
// 如果越过前一个边界,存活人数加1;当前位置排除掉存活人的位置
last--;
k++;
}
}
}
return (int) last;
}
上述代码对于m大的情况下会造成很多执行浪费,因此做以下优化。
public int lastRemaining(long n, long m) {
long last = m * (n - 0);
long k = 1;
long step = m - 1;
while (last > n) {
if (k >= m) {
long count = k / step;
k += count;
last = last - k;
if (last <= m * (n - k)) {
last--;
k++;
}
} else {
// 比较上述代码,增加每次的移动长度,减少执行次数。
long s = m / k;
last = last - (k * s);
if (last <= m * (n - k)) {
last--;
k++;
} else {
if (n > k) {
last = last - k;
last--;
k++;
}
}
}
}
// 边界问题,在上一步可能存在多减一次k;
if (last <= 0) {
last += k;
}
return (int) (last - 1);
}