题目
0,1,···,n-1这n个数字排成一个圆圈,从数字0开始,每次从这个圆圈里删除第m个数字(删除后从下一个数字开始计数)。求出这个圆圈里剩下的最后一个数字。
例如,0、1、2、3、4这5个数字组成一个圆圈,从数字0开始每次删除第3个数字,则删除的前4个数字依次是2、0、4、1,因此最后剩下的数字是3。
解题思路
这题很难,需要推导公式, 首先我们按照一步一步来,从n=5, m=3来做删除工作,直到留下最后的元素d, 所以我们最后的返回一定是元素d所在的下标, 那么我们是否可以获得每次删除后元素d所在的位置呢,请看下标变化的具体流程, 其中第二行是没有变化的下标,让他一直叠加,后面是给定的下标,也就是注释中第一部分的下标,f(n,m)返回是元素d所在的下标, 那么f(n, m) = (f(n-1, m) + m%n)%n = (f(n-1, m) + m)%n,注 m%n%n = m%n 可以得出这样的公式,公式解释如下首先如何获得删除的元素下标, 如果是删除第m个元素,其实被删除的就是m-1下标的元素,保留的就是m, 因为是闭环,m可以打过n, 所以这里使用m%n, 然后每次迭代删除一个元素, 元素就会减少1个, 所以这里的f(n-1, m)表示上一个元素所在的位置,再加上前面的m%n,在对其%n这个就是我们最后元素所在的位置了. 类似于迭代了, 我们还知道的一点是, 最后就剩一个元素了,其下标必为0,那f(1,m)=0,无论m是多少,然后我们就可以逆向推导上去一直到我们要的f(5,3)的结果了, 具体代码如下。
Java代码实现
public class LastRemaining {
/**
* 约瑟夫环问题
* n = 5, m = 3
* 第1次循环
* a b c d e
* 0 1 2 3 4
* 第2次循环
* d e a b
* 0 1 2 3
* 第3次循环
* b d e
* 0 1 2
* 第4次循环
* b d
* 0 1
* 第5次循环
* d
* 0
*
* 第2次开始下标开始变化,f(4,3) 原始变化如下
* --m-- d e a b (d e)
* 0 1 2 3 4 5 6 7 8
* 0 1 2 3 4 5
* 第3次再次原始变化如下:
* --m-- --m-- b d e (b d)
* 0 1 2 3 4 5 6 7 8 9 10
* 0 1 2 3 4
* 第4次再次原始变化如下:
* --m-- --m-- --m-- b d (b d)
* 0 1 2 3 4 5 6 7 8 9 10 (11 12)
* 0 1
* 第5次再次原始变化如下:
* --m-- --m-- --m-- ---m--- d
* 0 1 2 3 4 5 6 7 8 9 10 11 12
* 0
* f(n=5,m=3) 表示5个元素,删除第三个元素, 表示最后剩余元素d所有在的位置
* f(5,3) = (f(4,3) + m) % n = 3 % 5 = 3
* f(4,3) = (f(3,3) + m) % n = 4 % 4 = 0
* f(3,3) = (f(2,3) + m) % n = 4 % 3 = 1
* f(2,3) = (f(1,3) + m) % n = 3 % 2 = 1
* f(1,3) = (f(0,3) + m) % n = (0 + 3) % 1 = 0
*/
public int lastRemaining(int n, int m) {
return f(n, m);
}
private int f(int n, int m) {
if(n == 1){
return 0;
}
int x = f(n - 1, m);
return (m+x)%n;
}
}