题目描述
n个人(编号0~(n-1))围成一个圈,从0开始报数,报到(m-1)的退出,剩下的人 继续从0开始报数。求最后剩下的那个人的编号。
解题思路:用一个数组来记录每个人的状态(是否退出圈子),然后开始计数,数到m就改变这个位置人的状态,同时把总人数减去1。从新开始计数,知道总人数只剩一个人,此时遍历数组,找到状态没有改变的那个人输出。
public class Solution {
public int LastRemaining_Solution(int n, int m) {
int count = 0;
int people = n;
int i = 0;
int[] flag = new int[n];
while(people > 1){
if(flag[i] == 0){//如果状态为0,代表没有离开圈子,计数加1
count ++;
}
if(count == m){//如果计数到m,就把这个位置状态置1,计数归零,总人数减1。
flag[i] = 1;
count = 0;
people --;
}
i ++;
if(i == n){//数到最后一个数时候从头开始数
i = 0;
}
}
//遍历数组找到状态没有改变的那个人
for(int k = 0; k < flag.length; k ++){
if(flag[k] == 0){
return k;
}
}
return -1;
}
}
分析
这样的方法每次标记一个状态需要执行m次,一共要标记n-1次,所以时间复杂度是O(m*(n-1)),同时空间复杂度是O(n)。
接下来的内容都是复制一个大佬,原文链接如下:约瑟夫环的理解
约瑟夫环的介绍就不再叙述,自行百度一下。
这里拿a, b, c, d, e, f, g, h, i, j 来模拟10个人,设m=3.
下图的意思呢,就是模拟这10个人玩这个游戏到最后一个人存活下来的整个过程。绿色行表示在n = i 时,每个人的位置索引。因为在n = 10时,数一个m,杀死了c之后,后面的继续从0开始数,此时剩9个人,从0开始数,相当于这又是一个全新的游戏,只不过现在只剩9个人,而这9个人此时的位置索引也发生了变动。
通过上图,我们可以很直观的看到,最终d存活了下来。那么,此时我们从下往上看图,看看每一轮d的位置索引是什么情况。当n = 1, 2, 3, 4, 5, 6, 7, 8, 9, 10时,d分别选择了位置索引为:0, 1, 1, 0, 3, 0, 3, 6, 0, 3。什么意思呢?可以这么理解,现在让你参与玩这个游戏,你不想死,怎么办呢,要是有1个人参与,ok,你不用死。要是总共2个人参与,你必须选择位置索引为1才能存活下来;要是总共有3个人参与,你必须选择位置索引为1才能存活;要是总共有4个人,你必须选择0…依次类推。你必须通过n来推算自己的索引位置才可以存活下来。
那么我们此时用f(n)表示n个人的时候,能够存活的位置索引。由上图显然可以得到f(1)=0,f(2)=1,…,f(10)=3。但是我们要找的是通项公式。从图中也可以很直观的看到,n=9时d的位置索引就是n=10时循环左移了3位。那么可以得到f(10) = (f(9) + 3) % 10,因为是循环左移,所以需要加一个取余,防止越界。同理可以得到f(3) = (f(2)+3)%3,f(2) = (f(1) + 3) % 2。
从始至终,m只是一个常量。所以不影响结论。
所以约瑟夫环问题的公式:
f(n,m) = (f(n-1, m) + m) % n
代码如下:
public class Solution {
public int LastRemaining_Solution(int n, int m) {
if(n < 1 || m < 1)
return -1;
int last = 0;
for(int i = 2; i <= n; i ++){
last = (last + m) % i;
}
return last;
}
}
迭代时我们可以直接从i = 2 开始,因为 i = 1时存活的就是你自己,编号就是0,如果大于1就按照递推公式依次依次算出来i = n 时候的结果。