方法一:
用数组下标表示每个人的位置,数组元素0代表环内、1代表出环。
public int lastRemaining(int n, int m) {
int[] arr = new int[n];
int num = 0;
int index = 0;
int count = n;
int res = 0;
while (count > 0) {
num += (arr[index] == 0 ? 1 : 0);
if (num >= m) {
arr[index] = 1;
num = 0;
count--;
res = index;
}
index = (index + 1) % n;
}
return res;
}
但这种纯遍历在数据多了后会导致超时。
方法二:
假循环链表:
约瑟夫环简单出环问题可以用链表的改节点指向实现。
但假链表更加方便:依旧用数组存储,但下标表示其指向的下一个元素的下标,遍历时依据元素值遍历。
public int lastRemaining(int n, int m) {
int[] people = new int[n];
int count = n;
int num = 0;
int res = 0;
// for (int i = 0; i < n; i++) {
// people[i] = (i + 1) % n;
// }
for (int i = 0; i < n - 1; i++) {
people[i] = i + 1;
}
int curIndex = 0;
int preIndex = (n - 1 + count) % count;
while (count > 0) {
num++;
if (num >= m) {
res = curIndex;
people[preIndex] = people[curIndex];
num = 0;
count--;
} else {
preIndex = curIndex;
}
curIndex = people[curIndex];
}
return res;
}
这种方法在m<n时,时间效率更高,但是当n < m,且数据很大时,仍然超时。
方法三:
用动态规划的思想:
求解f(n),当出队列一个人时,即又求解f(n-1)
此时,设前一个出队人的下标为t
f(n)=(f(n−1)+t)%n
=(f(n−1)+m%n)%n
=(f(n−1)+m)%n
反向推就是,当环内只有一个人,一定是下标为0出队列,之后每次出队列都是在前一个人的(下标上进行+m) % 当前n操作
public int lastRemaining(int n, int m) {
int x = 0;
for (int i = 2; i <= n; i++) {
x = (x + m) % i;
}
return x;
}