zoukankan      html  css  js  c++  java
  • Java实现约瑟夫环问题

    有朋友去浦发面试,因为我们是相同岗位,为了查漏补缺,便问了一下他们的机试题目。

    机试考3道编程,前两道很水,最后一道他说有点麻烦,没有AC。我自己也尝试着码了一下,然后发现还是得需要耐心。

    在此,我列出了三种方法,以供大家参考。

    其中包括标号从0 开始的(0....(N-1)),和标号从1开始的(1....N))两个版本。

    先简单说一下我的思路,前两种方法就是模拟整个过程:

    1. 标号从1开始:

    用动态数组存储数据,一个大循环就是数组不为空;然后利用 target = (target + k)%list.size(); 求出数组下标,对list.size()取模是为了不让target越界;那么得到的target的范围就是【0,list.size()】,然后就打印 list.get(target-1) ,所以,为了不越界,需要判断  target!=0 ,然后,再将该元素移除 list.remove(target-1);  ,最后再将下标自减1(关于为什么将下标再减一,下面第2种方法中有解释)。如果 target==0 的话,那么就打印数组中最后一位 list.get(list.size()-1) ,然后再移除该元素即可。

    代码如下:

    private static void lastPeople(int total, int k) {
            //初始化人数,人数排号从1开始;
            List<Integer> list = new ArrayList<Integer>();
            for (int i = 1; i <= total; i++) {
                list.add(i);
            }
            //从第target(数组下标)个开始重新数数;
            int target = 0;
            while (!list.isEmpty()) {
                target = (target + k)%list.size();
                //当target=0时,target-1就是-1了,数组越界,其意思就是返回倒数第一个元素,即list.size()-1;
                if (target != 0) {
                    System.out.print(list.get(target-1)+" ");
                    list.remove(target-1);
                    target--;
                }else {
                    System.out.print(list.get(list.size()-1)+" ");
                    list.remove(list.size()-1);
                }
            }
            
        }

    2. 标号从0开始:

    同样的,用动态数组存储数据,一个大循环就是数组不为空;用 i 记录已走过的整个数组下标的最后一位(关于这句话,大家可能不太理解,意思就是比如数组[1,2,3,4,5,6],当遍历到元素4时,下标为3,然后把4移除,下标自减,让其改为2,指向元素3,而不是指向还未遍历到的元素5);以count计数,到第k个就删去该元素,并且置count=0,i-- 使得下标回退一步。

    代码如下:

    private static void lastPeople1(int total, int k) {
            //初始化人数
            List<Integer> list = new ArrayList<Integer>();
            for (int i = 0; i < total; i++) {
                list.add(i);
            }
            int i = -1;//记录整个序号下标
            int count = 0;//记录第几个,是否到达第k个;
            while (list.size() != 0) {
                ++i;
                if (i == list.size()) {
                    i = 0;
                }
                ++count;
                if (count == k) {
                    System.out.print(list.get(i) +" ");
                    list.remove(i);
                    count=0;
                    --i;
                }
            }
            
        }

    3. 第三种方法是我从网上找来的,但是该方法不能打印整个过程,只能选择出最后一位被枪毙的人是谁。是用数学规律解的,本人表示服气。

    推出的递归公式是: F(i)=F(i-1)+ k  ,为了防止数组越界,需要取模  F(i)=(F(i-1)+ k)%i  。

    代码如下:

    private static void lastPeople2(int n, int k) {
        int res = 0;
        for (int i = 2; i <= n; i++) {
            res = (res + k)%i;
        }
        System.out.print((res+1)+" ");
    }

    三种方法输出结果如下:

    以上我都是用动态数组arraylist来存储数据的,我在网上查到还有用队列解决的,方法也很好,在这里我就不多说了,详见参考4.

    又来更了。。。

    最近又看了一篇专门写约瑟夫环的一个公众号文章:

    里面的解法相当给力:

     方法4. 递归:

    递归是思路是:每次我们删除了某一个士兵之后,我们就对这些士兵重新编号,然后我们的难点就是找出删除前和删除后士兵编号的映射关系

    我们定义递归函数 f(n,m) 的返回结果是存活士兵的编号,显然当 n = 1 时,f(n, m) = 1。假如我们能够找出 f(n,m) 和 f(n-1,m) 之间的关系的话,我们就可以用递归的方式来解决了。

    我们假设人员数为 n, 报数到 m 的人就自杀。则刚开始的编号为:(从1开始编号)

    1

    m - 2

    m - 1

    m

    m + 1

    m + 2

    n

    进行了一次删除之后,删除了编号为 m 的节点。删除之后,就只剩下 n - 1 个节点了,删除前和删除之后的编号转换关系为:

    删除前     ---     删除后

    …            ---      …

    m - 2       ---     n - 2

    m - 1       ---      n - 1

    m            ---    无(因为编号被删除了)

    m + 1      ---     1(因为下次就从这里报数了)

    m + 2      ----     2

    …            ----         …

    新的环中只有 n - 1 个节点。且删除前编号为 m + 1, m + 2, m + 3 的节点成了删除后编号为 1, 2, 3 的节点。

    假设 old 为删除之前的节点编号, new 为删除了一个节点之后的编号,则 old 与 new 之间的关系为  old = (new + m - 1) % n + 1。 为什么加1后面解释。

    这样,我们就得出 f(n, m) 与 f(n - 1, m)之间的关系了,而 f(1, m) = 1。所以我们可以采用递归的方式来做。代码如下:

    int f(int n, int m){
        if(n == 1)   return n;
        return (f(n - 1, m) + m - 1) % n + 1;
    }

    //或者
    int f(int n, int m){
        return n == 1 ? n : (f(n - 1, m) + m - 1) % n + 1;
    }

    时间复杂度是 O(n),空间复杂度是O(1)。

    注:有些人可能会疑惑为什么不是 old = (new + m ) % n 呢?

    主要是因为编号是从 1 开始的,而不是从 0 开始的。如果 new + m == n的话,会导致最后的计算结果为 old = 0。所以 old = (new + m - 1) % n + 1.

    Over...

    参考:

    1.  约瑟夫环问题

    2. 约瑟夫环Java实现

    3. Java实现约瑟夫环问题

    4. 数据结构(二)java解决约瑟夫环的两种方法(数组和队列)

    5. 一道阿里笔试题:如何用一行代码解决约瑟夫环问题的

  • 相关阅读:
    hadoop学习笔记(十):MapReduce工作原理(重点)
    hadoop学习笔记(九):MapReduce程序的编写
    hadoop学习笔记(八):MapReduce
    hadoop学习笔记(七):Java HDFS API
    hadoop学习笔记(六):HDFS文件的读写流程
    hadoop学习笔记(五):HDFS Shell命令
    hadoop学习笔记(四):HDFS
    hadoop学习笔记(三):hadoop文件结构
    立即执行函数
    let命令
  • 原文地址:https://www.cnblogs.com/gjmhome/p/11422623.html
Copyright © 2011-2022 走看看