约瑟夫问题获取第x个出局人o(x)算法

187 阅读1分钟

n个人,报数到m的出局; 可以理解为n*m的数组,每个报数为m的倍数的人出局。 接下来就是找到每次出局一个人后后续人员的排序规律。 假设n为5,m为3;

如下图所示:

  1. 出局第一个人后,1和2移动到6和7的位置;
  2. 出局第二个人后,4和5移动到8和9的位置上;
  3. 出局第三个人后,2和4移动到10和11的位置;
  4. 出局第四个人后,4移动到13的位置,一直移动到15位置,作为最后一个出局点。 image.png
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);
    }