笔试碰到约瑟夫环的我不用愁啦

94 阅读3分钟

题目

N个人围成一圈,第一个人从1开始报数,报M的将被杀掉,下一个人接着从1开始报。如此反复,最后剩下一个,求最后的胜利者。

解题思路

  1. 模拟
  2. 数论公式

模拟

设定一个数组存储所有人的状态,遍历整个数组,每次遍历了m个人之后将第m个人打上标记,在遍历过程中跳过已标记的人,若是遍历完数组后,从数组头开始重新遍历,但是计数【计算此时遍历了几个无标记的人】不变。直到打了n-1个标记后停止数组遍历。

之后遍历数组,将第一个没有打标的人的编号作为答案

数论公式推导

推导过程

若 n 为 1,则可以轻松得到最终编号为 1

我们由已知推未知,可以尝试一下能否一步一步从 n = 1 倒推到 n = N。所以我们假设 m 不变,值为 M 。

我们假设编号为 n1 的人幸存到最后,之后幸存的人按存活时间长远与否编号,依次为 n2...nN 。n1 获得最长以此类推。

那么每次我新加入一个人时,原来的编号次序的前后顺序应当是不变的。比如:

当只有两个人时,假设编号顺序为 n2 n1 时,才能保证最后存活的人是 n1。

那么加入 n3 后,我们应该首先保证第一次牺牲掉的 人是 n3 ,然后 n1 n2 的原有顺序不变,这样才能保证好次序。所以 n3 就应该添加在 M % n 处,才能保证第一个死去的是 n3 。因为 n 可能比 M 小,所以需要进行取模。

那么相比较 n = 2 时,此举相当于给整个次序前方加了 M 个数,这 M 个数的最后一个数为 n3 。

然后这 M 个人在 n = 3 处死第一个人,状态回归到 n = 2 时,消失了。

一群胆小的墙头草啊,就这么没骨气的跑了,o`(>﹏<)′

以后的规律也是这样。所以我们可以得到 n = N 是在 n = N - 1 的幸存编号上多加了 M ,但是要保证幸存编号在 N 以内,所以应该对 N 取模。

那么假设 f(N, M) 为最终答案,那么推导公式为 f(N, M) = (F(N - 1, M) + M) % N

结论

f(N, M) = (F(N - 1, M) + M) % N

题解

模拟

function solution2(n, m) {
  const people = new Array(n).fill(false);
  let count = 0, deadPeople = 0, i = 0;
  while (deadPeople !== n - 1) {
    if (i >= n) i = i % n;
    if (!people[i] && ++count === m) {
      console.log(i + 'dead')
      count = 0;
      people[i] = true;
      deadPeople++;
    };
    i++;
  }
  for(let i = 0; i < n; i++) {
    if(!people[i]) return i + 1;
  }
}

数论

function solution(n, m) {
  let res = 0;// 只有一个人的时候那个人就是赢家
  for (let i = 2, len = n; i <= n; i++) {
    res = (res + m) % i;
  }
  // 得到的仅仅是下标,对应序号的话要进行转换
  return res + 1;
}