问题描述
标题:约瑟夫环
n 个人的编号是 1~n,如果他们依编号按顺时针排成一个圆圈,从编号是1的人开始顺时针报数。
(报数是从1报起)当报到 k 的时候,这个人就退出游戏圈。下一个人重新从1开始报数。
求最后剩下的人的编号。这就是著名的约瑟夫环问题。
本题目就是已知 n,k 的情况下,求最后剩下的人的编号。
题目的输入是一行,2个空格分开的整数n, k
要求输出一个整数,表示最后剩下的人的编号。
约定:0 < n,k < 1000000
例如输入:
10 3
程序应该输出:
4
资源约定:
峰值内存消耗(含虚拟机) < 256M
CPU消耗 < 1000ms
请严格按要求输出,不要画蛇添足地打印类似:“请您输入…” 的多余内容。
代码实现
最后一种方法纯属自己瞎想,如有错误请指教
数组法
- 分析
1.用数组记录所有人编号
2.当某人出局时,设置值为-1
3.模拟整个游戏过程,直到所有人出局为止
import java.util.Scanner;
public class Josephus {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int k = sc.nextInt();
sc.close(); //用完关闭,免得占资源
int[] arr = new int[n];
for (int i = 0; i < arr.length; i++) {
arr[i] = i + 1; //初始化所有人的编号
}
int cut = 0; //计数器
int outNum = 0; //出局人数
int out = 0; //记录当前出局的编号
for (int i = 0; outNum < arr.length; i++) { //枚举所有人的编号
if (i == arr.length) { //设计成环状
i = 0;
}
if (arr[i] == -1) { //跳过已经出局的人
continue;
}
cut++; //报数
if (cut == k) {
cut = 0; //还原计数器
out = arr[i]; //记录当前出局者
arr[i] = -1; //-1表示已出局
outNum++; //出局人数+1
// System.out.println(out); //打印每次出局的人
}
}
System.out.println(out); //打印最后一个人
}
}
链表法
import java.util.Scanner;
public class JosephusLinked {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int k = sc.nextInt();
sc.close(); //用完关闭,免得占资源
CircleLinked linked = new CircleLinked(n); //创建链表
int cut = 1; //计数器
Node currentNode = linked.getFirst();
while (true) {
Node nextNode = currentNode.getNext(); //获取下一个节点
if (currentNode == nextNode) {
break; //如果当前节点和下一个节点相同,表示只剩下一个人,游戏结束
}
cut++; //报数
if (cut == k) { //满足报数条件,出局
currentNode.setNext(nextNode.getNext()); //让当前节点的下一节点指向下下节点
// System.out.println(nextNode.getData()); //打印本回合出局的编号
cut = 0;
} else {
currentNode = nextNode;
}
}
System.out.println(currentNode.getData()); //游戏结束,打印最后一个人的编号
}
}
class CircleLinked { //环形链表类
private Node first = null;
public CircleLinked(int num) { //增加人数
if (num < 1) {
return;
}
Node currentNode = null;
for (int i = 1; i <= num; i++) {
Node node = new Node(i);
if (i == 1) { //如果是第一个节点,让first节点指向该节点,并让当前节点指向first
first = node;
currentNode = first;
}
currentNode.setNext(node); //让当前节点的下一个节点指向node
node.setNext(first); //让node的下一个节点指向第一个节点
currentNode = node; //让当前节点指向node
}
}
public Node getFirst() {
return first;
}
}
class Node { //节点类
private int data; //记录编号
private Node next; //记录下一个节点
public Node(int data) {
this.data = data;
}
public int getData() {
return data;
}
public void setData(int data) {
this.data = data;
}
public Node getNext() {
return next;
}
public void setNext(Node next) {
this.next = next;
}
}
递推公式法
显然,无论是数组法还是链表法都是模拟整个游戏过程,写起来很麻烦,而且时间复杂度非常高,当处理很大的数据时,短时间无法给出答案,而题目只要求给出最后一个编号即可,所以可以减掉不必要的过程。
n个人从第1个人开始报数,当报到k时出局,第一个出局的人就是k%n,此时变成了n-1个人从第(k+1)个人开始报数,报到k时出局,只需求出n-1个人最后剩下第几个人再加上k之后对n取余,便是n个人最后的结果,用切蛋糕思维,将大环变成小环,
设f(n)表示n个人组成的约瑟夫环最后剩下人的编号,取余为0时表示编号最大的那个人;
f(1) = 1
f(2) = (f(1) + k) % 2
f(3) = (f(2) + k) % 3
…
f(n) = (f(n - 1) + k) % n
亲测有效,错了请指教
import java.util.Scanner;
public class JosephusRecursive {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int k = sc.nextInt();
sc.close();
int f = 1; //初始化f表示当n = 1时,最后剩下的人
for (int i = 2; i <= n; i++) { //递推公式,从f(2)推到f(n)
f = (f + k) % i;
if (f == 0) {
f = i;
}
}
System.out.println(f);
}
}