题目
N个人围成一圈,第一个人从1开始报数,报M的将被杀掉,下一个人接着从1开始报。如此反复,最后剩下一个,求最后的胜利者。
解题思路
- 模拟
- 数论公式
模拟
设定一个数组存储所有人的状态,遍历整个数组,每次遍历了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;
}