约瑟夫环问题
热身
相传在罗马帝国时期,有一群犹太士兵被罗马人包围,犹太士兵不想投降,然后相约自杀。自杀的规则是所有人围成一个圈,1号位置的士兵先杀掉2号位置士兵,3号位置的士兵杀掉4号位置的士兵,5号位置的士兵杀掉6号位置的士兵.... 一轮完成后,开启下一轮,再由1号位置的士兵杀掉他左边存活的士兵....直至圈中剩余最后一个人进行自杀。其中有个叫约瑟夫的人就在这群士兵中,他不想死,他计划留到最后称为最后一个人,然后向罗马士兵投降。如果你是约瑟夫你会怎么办,选择不同的站位是否会对结果有影响? 那个站位又确保能够活到最后呢?
当总人数较少的情况下,最后存活的人还是比较好推算的?
总数为1,1号位存活
总数为2,1号位存活
总数为3,3号位存活
总数为4,1号位存活
总数为5,3号位存活
总数为6,5号位存活
总数为7,7号位存活
总数为8,1号位存活
......
可以看到首先由1号位发起进攻时,无论士兵总数为多少,最终存活下来的都是奇数为的士兵。而其中1号位的士兵存活的几率更大一些,对于总数为2^n时,总是1号位最后存活。
总数不是2^n也可以通过转化为2^n的形式进行求取最后的存活位次。如总数为5=2^2+1 ,多出的这一个1,可以让1号位先杀掉一个人,这时指针指到3号位,士兵总数就是2^2了。而对于总数为2^n的情况又总数第一个位置即现在的3号位存活到最后。所以总数为5人时,最终存活的是3号位。
由此可以推算出数学公式:
x=2^n+a x-总人数 a-总人数拆分2^n后的剩余人数
y=2a+1 y=最终存活人的位置
拓展的约瑟夫环问题
拓展的约瑟夫环问题,不再是固定的移除直接相邻的人,而是可以自定义间隔人数。
约瑟夫环(Josephus Problem)是一个经典的理论问题,通常描述为:有n个人站成一个圈,从某个点开始报数,每报到第m个人就将那个人移除出圈。然后从被移除的人的下一个开始重新报数,继续这个过程,直到最后剩下一个人。问题是即最后剩余的人的位置?
解决这个问题可以通过递归或者迭代的方法来实现。下面是两种方法的详细解释:
递归方法
递归方法基于以下观察:
- 如果我们知道对于n-1个人的答案,那么对于n个人的情况,答案将是 (J(n-1, m) + m) % n。
- 基本情况是当只有1个人时,其位置显然是0(如果我们以0作为起始编号)。
递归公式可以表示为:
[ J(n, m) = (J(n-1, m) + m) % n ]
其中 (J(1, m) = 0)。
迭代方法
迭代方法避免了递归调用可能导致的栈溢出问题,并且在空间复杂度上更加高效。基本思想是从2个人开始逐步计算到n个人的情况。
这里给出一个java语言的迭代方法实现:
public int findTheWinner(int n, int k) {//总人数为1时 第一个位置为获胜者int res=0;for(int i=2;i<=n;i++){/*** 本轮获胜位置为上一轮获胜的位置加k 取模本轮总人数的值* 1.可以将总数为i人的本轮游戏转化为上一轮游戏。 先移除1人 则与总数为i-1的上一轮游戏开始时一致* 2.只不过最终获胜者的位置需要转换为本轮新编号的位置(与上一轮相差k)*/res=(res+k)%i;}return res+1;
}