约瑟夫问题_java_如有错误请指教

问题描述

标题:约瑟夫环
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);
	}
}

猜你喜欢

转载自blog.csdn.net/weixin_48598155/article/details/106746399