已知n个人(以编号1,2,3...n分别表示)围坐在一张圆桌周围。从编号为k的人开始报数,数到m的那个人出列;他的下一个人又从1开始报数,数到m的那个人又出列;依此规律重复下去,直到圆桌周围的人全部出列。求最后一个出列的人的编号。
用ArrayList解,每次算出出列的人的编号,从list中移除。这样的时间复杂度是O(MN),N为人数,M为报数的那个数。
代码来自 https://baike.baidu.com/item/%E7%BA%A6%E7%91%9F%E5%A4%AB%E7%8E%AF
import java.util.ArrayList;
import java.util.List
import java.util.Scanner;
public class Yue {
public static void main(String[] args) {
// Scanner scanner = new Scanner(System.in);
// System.out.print("请输入总人数:");
// int totalNum = scanner.nextInt();
// System.out.print("请输入报数的大小:");
// int cycleNum = scanner.nextInt();
// System.out.print("请输入开始编号:");
// int startNO= scanner.nextInt();
// yuesefu(totalNum, cycleNum,startNO);
yuesefu(3, 7,1);
}
public static void yuesefu(int totalNum, int countNum,int startNO) {
// 初始化人数
List<Integer> start = new ArrayList<Integer>();
for (int i = 1; i <= totalNum; i++) {
start.add(i);
}
//从下标为K开始计数
int k = startNO-1;
while (start.size() >0) {
System.out.println(start);
//第m人的索引位置
k = (k + countNum) % (start.size()) - 1;
// 判断是否到队尾 到队尾时候k=-1
if (k < 0) {
System.out.println(start.get(start.size()-1));
start.remove(start.size() - 1);
k = 0;
} else {
System.out.println(start.get(k));
start.remove(k);
}
}
}
}
解法二:http://blog.csdn.net/wuzhekai1985/article/details/6628491 具体解法看这里
class Main {
public static void main(String[] args) {
System.out.println(JosephusProblem(10, 3));
}
public static int JosephusProblem(int N, int M){
if(N < 1 || M < 1) return -1;
int[] dp = new int[N+1];
dp[0] = dp[1] = 0;
for(int i = 2; i <= N; i++){
dp[i] = (dp[i-1] + M)%i;
}
return dp[N]; //小孩的编号从0开始算起
}
}
下面利用数学推导,如果能得出一个通式,就可以利用递归、循环等手段解决。下面给出推导的过程:
(1)第一个被删除的数为 (m - 1) % n。
(2)假设第二轮的开始数字为k,那么这n - 1个数构成的约瑟夫环为k, k + 1, k + 2, k +3, .....,k - 3, k - 2。做一个简单的映射。
k -----> 0k+1 ------> 1
k+2 ------> 2
...
...
k-2 ------> n-2
这是一个n -1个人的问题,如果能从n - 1个人问题的解推出 n 个人问题的解,从而得到一个递推公式,那么问题就解决了。假如我们已经知道了n -1个人时,最后胜利者的编号为x,利用映射关系逆推,就可以得出n个人时,胜利者的编号为 (x + k) % n。其中k等于m % n。代入(x + k) % n <=> (x + (m % n))%n <=> (x%n + (m%n)%n)%n <=> (x%n+m%n)%n <=> (x+m)%n
(3)第二个被删除的数为(m - 1) % (n - 1)。
(4)假设第三轮的开始数字为o,那么这n - 2个数构成的约瑟夫环为o, o + 1, o + 2,......o - 3, o - 2.。继续做映射。
o -----> 0
o+1 ------> 1
o+2 ------> 2
...
...
o-2 ------> n-3
这是一个n - 2个人的问题。假设最后的胜利者为y,那么n -1个人时,胜利者为 (y + o) % (n -1 ),其中o等于m % (n -1 )。代入可得 (y+m) % (n-1)要得到n - 1个人问题的解,只需得到n - 2个人问题的解,倒推下去。只有一个人时,胜利者就是编号0。下面给出递推式:
f [1] = 0;
f [ i ] = ( f [i -1] + m) % i; (i>1)
有了递推公式,实现就非常简单了,给出循环的两种实现方式。再次表明用标准库的便捷性。
作者:郑肥脸
链接:https://www.zhihu.com/question/20065611/answer/132185200
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
可以理解这道题:
当有n个人时,报数为m的人编号为(m - 1)%n,出列,这算一轮;接下来有n - 1个人,这回是从编号为m%n的人开始报数,这次是第一轮编号为[m + (m - 1) % (n-1)]%n的人出列,如果每一轮编号都从开始报数的那个人开始算,那么第二轮出列的人在第二轮的编号是(m-1)%(n-1),剩下各轮也是这个套路。
很明显,对于每一轮而言,当前轮出列的人可以有两个编号,一个编号是从上一轮开始报数的人开始算的编号,一个是从这一轮开始报数的人的编号。
从第一轮到最后一轮(只剩一个人),我们的目标都是一样的,就是求最后一轮的人在第一轮中的编号。相邻两轮的差别在于,开始报数的人不一样,总人数也不一样,而这种差别同样适用于最后一轮和倒数第二轮,也就是说,对于最后一轮来说,最后留下来的人正是最后一轮开始报数的人。
那么问题就简单了,我们只需要求出相邻的两轮之间,同一个人在后一轮的编号和在前一轮里的编号(再次强调,这个编号不是指第一轮里的编号,而是任意一轮,从开始报数的人开始算的编号)
最后一轮报数的人在最后一轮的编号为0,那么这个人在倒数第二轮里的编号是多少就可以求出来,求出倒数第二轮的对应编号,那么这个人对应到倒数第三轮是多少也可以求出来……直到对应到第一轮,因为我们最后想要的,就是以第一轮编号为基准的最后留下的人的编号。