约瑟夫环问题介绍
来自百度百科:约瑟夫环(约瑟夫问题)是一个数学的应用问题:已知n个人(以编号1,2,3...n分别表示)围坐在一张圆桌周围。从编号为k的人开始报数,数到m的那个人出列;他的下一个人又从1开始报数,数到m的那个人又出列;依此规律重复下去,直到圆桌周围的人全部出列。通常解决这类问题时我们把编号从0~n-1,最后 结果+1即为原问题的解。
题目比较简单,有两种解题思路。分别为:按照减人逻辑一步一步进行(普通的逻辑);通过数学公式进行逆推,由1推2,推3,直至推n;
第一种思路:
直接根据普通逻辑,建立循环队列或者数组,一步一步减人并进行重新排序。排序之后再进行减人,直至剩余一人。
假设10人,即n=10,序号为4,即m=4;
第一次需要删去的人的序号:m-1 = 3
0 1 2 3 4 5 6 7 8 9
第二次需要删去的人的序号:((上一次删去的序号)+(m-1)) / n-1 =(3+3)/9 = 6 注:得出序号为6只是原来10个数的第六个,可现在数组已经减少了一个数, /9是为了进入循环,也可以理解为防止溢出。功能是一样的。
0 1 2 3 4 5 6 7 8 9 // 原来的序号
6 7 8 0 1 2 3 4 5 //删除后重新排序的序号
0 1 2 3 4 5 6 7 8 // 现在的序号
以此类推,第n-1次删去的人的序号: (n-2次删去的序号)+(m)) / 现在的数组长度。
代码如下:
public int LastRemaining_Solution(int n, int m) {
if(n==0 || m ==0) return -1;
LinkedList<Integer> list = new LinkedList<>();
int re_index = 0;//记录删去孩子的序号
for(int i=0;i<n;i++){
list.add(i);
}
while(list.size()!=1){
re_index = (re_index+ m -1) % list.size();
list.remove(re_index);
}
return list.get(0);
}
第二种思路
根据数学公式进行逆推。 f(n,m) = f(n-1,m) + m / n;
f(n,m) 表示n个人时,能够存活下来那个人的位置坐标(索引)。
f(1)=0, f(2) = (f(1) +m)/2;以此类推。推导过程比较繁琐,后续加入。
代码如下:
public int LastRemaining_Solution(int n, int m) {
if(n == 0 || m== 0) return -1;
int last = 0;
if(n==1) return 0;
for(int i=2;i<=n;i++){
last = (last+m) % i;
}
return last;
}
果然,做编程题要多想想,只有想通了逻辑,才会加快写代码的速度。每一个编程题背后,一定存在某个数学逻辑问题,要多想想才可。