算法题(三十):约瑟夫环问题

版权声明:转载请注明来处 https://blog.csdn.net/qq_24034545/article/details/84072792

问题描述

约瑟夫问题是一个非常著名的趣题,即由n个人坐成一圈,按顺时针由1开始给他们编号。然后由第一个人开始报数,数到m的人出局。现在需要求的是最后一个出局的人的编号。

给定两个int nm,代表游戏的人数。请返回最后一个出局的人的编号。保证n和m小于等于1000。

测试样例

输入:5 3
返回:4

分析

有两种方法可解这道题,一种是直接模拟该过程,找到最后一个数;另一种是利用数学归纳法来求解。

数学归纳法过程:

把n个人的编号改为0~n-1,然后对删除的过程进行分析。

第一个删除的数字是(m-1)%n,记为k,则剩余的编号为(0,1,...,k-1,k+1,...,n-1),下次开始删除时,顺序为(k+1,...,n-1,0,1,...k-1)。

f(n,m)表示从0~n-1开始删除后的最终结果

f(n-1,m)表示从(k+1,...,n-1,0,1,...k-1)开始删除的最终结果

f(n,m)=f(n-1,m)

扫描二维码关注公众号,回复: 4166147 查看本文章

把从(k+1,...,n-1,0,1,...k-1)转换为(0~n-2)的形式

k+1  0

k+2  1

...

k-1  n-2

转换函数为

p(x)=(x-k-1)\%n

假设我们知道(n-1)个人报数的问题的解x,那么根据上式把x变回去刚好就是n个人情况的解。用逆函数来变回去

{p(x)}'=(x+k+1)\%n

如何知道(n-1)个人报数的解呢?需要知道(n-2)时的情况,这样一直倒推到1。

所以

f(n,m)=f(n-1,m)={p(f(n-1,m))}'=(f(n-1,m)+k+1)\%n

k=(m-1)\%n

f(n,m)=(f(n-1,m)+m)\%n

令f[i]表示i个人玩报m退出最后的胜利者的编号,最后的结果自然是f[n]。

f[1]=0

f[i]=(f[i-1]+m)%i

最终输出 f[n]+1(编号从1开始)

上述过程的抽象理解方式为给定数n,m后,最终结果是固定的,只是在每次删除时,编号不一样,而我们要找到其在初始时的编号。那么就需要用逆函数来计算。先从最终结果逆运算到上一结果,同志逆运算到上上一个,一直逆运算到最后,即可得到在结果在初始时的编号。

public class YSFRound {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		int n = 9;
		int m = 5;
		fun(n, m);
	}
	//数学归纳
	public static void fun(int n, int m){
		if(n<0 || m<0){
			System.out.println(-1);
			return;
		}
		int s=0;
		for(int i=2; i<=n; i++){
			s = (s+m)%i;
		}
		System.out.println(s+1);
	}
    //模拟方法
	public static void fun2(int n, int m){
		LinkedList<Integer> list = new LinkedList<>();
		for(int i=1; i<=n; i++){
			list.add(i);
		}
		int idx=0;
		while(list.size()>1){
			int del = (idx+m-1)%list.size();
			list.remove(del);
			idx = del%list.size();
		}
		System.out.println(list.get(0));
	}
	

}

输出:8

猜你喜欢

转载自blog.csdn.net/qq_24034545/article/details/84072792