约瑟夫环问题

已知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         ----->  0 
             k+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,那么这个人在倒数第二轮里的编号是多少就可以求出来,求出倒数第二轮的对应编号,那么这个人对应到倒数第三轮是多少也可以求出来……直到对应到第一轮,因为我们最后想要的,就是以第一轮编号为基准的最后留下的人的编号。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值