不废话,直接上题目:

有2种解法(这里把他抽象出来,有n个人,报数到m就去除在外)

  • 一种是模拟。 可用数组模拟,也可用循环链表模拟。
  • 还有一种是 找规律。

找规律

先来看找规律的吧,模拟的后面直接贴代码了。

先来想一个问题,报数到m的人就去除,一直重复下去,这个算不算是一种规律😂。。。
如果有规律的话,我们通过和题目有关的 n、m2个数据,进行一些运算,就可以得出最后留下来的是哪个编号了。。。

目前确定的只有1种情况,就是如果只有一个人的话,不用报数,他直接留下来。

再来看看如果2个人的情况呢、3个人的情况呢、4个人、5个人、…… n-1个人、n个人的情况呢。(当然这后面都与要报的数是多少有关了,我们暂且先不管这个)
把这些情况都抽象出来,我们能不能找到 i 个人的情况 和 i-1 个人的情况,这时候留下来的编号是多少(这个结果肯定是带有m这个未知数的),找出他们这样相邻之间的关系?

用图解的方式说明吧:


主要难点就是在找规律,这个也是已经得到答案后,倒推出来的,想了大半天。。。
接下来就直接上代码吧。。。

/** * 找出规律,用递归解决 */
public class JosephusProblem {
   

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

    public static void main(String[] args) {
   
        // 报1就去除,留下的总是最后一个。
        for (int i = 1; i <= 10; i++) {
   
            System.out.println(getLive(i, 1));
        }
        System.out.println("====================");

        // 报3去除
        for (int i = 1; i <= 10; i++) {
   
            System.out.println(i + "个人,留下来的是:" + getLive(i, 3));
        }
    }
}

数组模拟:

模拟的,(没什么好说的,思路都在代码里了。。。)

/** * 用数组模拟解决, 1表示该位置有人,0表示该位置没人。 */
public class JosephusProblemByArray {
   

    /** * 数组中,是不是 只剩下了 一个1元素,其他都是0元素。 */
    public static boolean isSoleOne(int[] people) {
   
        int oneNumber = people.length;
        for (int i = 0; i < people.length; i++) {
   
            if (people[i] == 0) {
   
                oneNumber--;
            }
        }
        return oneNumber == 1;
    }

    /** * 找到数组中 , 第一个为 1 的下标。 */
    public static int firstOne(int[] people) {
   
        for (int i = 0; i < people.length; i++) {
   
            if (people[i] == 1) {
   
                return i;
            }
        }

        // 不可能进到这种情况,随便写了个数字
        return -1;
    }

    /** * 当前下标为 index, 这个位置是不是 数组中的最后一个为 1 的下标。(不循环来看的) */
    public static boolean isEndOne(int[] people, int index) {
   
        if (index == people.length - 1) {
   
            return true;
        }
        for (int i = index + 1; i < people.length; i++) {
   
            if (people[i] == 1) {
   
                return false;
            }
        }
        return true;
    }

    /** * 当前下标为 index,寻找数组中下一个为1的下标。 */
    public static int nextOne(int[] people, int index) {
   
        if (isEndOne(people, index)) {
   
            return firstOne(people);
        }

        for (int i = index + 1; i < people.length; i++) {
   
            if (people[i] == 1) {
   
                return i;
            }
        }

        // 不可能进到这种情况,随便写了个数字
        return -1;
    }

    /** * 用数组模拟所有人都情况:(初始时,所有元素都为 1) * 1:表示有人。 * 0:表示没人。 * * 返回 剩下一个人的序号。(从1开始计数的。) */
    public static int process(int[] people) {
   
        int number = 1;     // 报数的 数字。
        int index = 0;      // 数组中的下标 数字。

        // 只要不是剩最后一个1元素,就一直进行报数循环。
        while (!isSoleOne(people)) {
   
            if (number == 1) {
   
                number++;
            } else if (number == 2){
   
                number++;
            } else if (number == 3) {
   
                people[index] = 0;
                number = 1;
            }

            index = nextOne(people, index);

        }

        // 只剩下了最后一个1元素,找到他的下标。
        // 因为需要的是从1计数的,所以返回下标+1即可。
        for (int i = 0; i < people.length; i++) {
   
            if (people[i] == 1) {
   
                return i+1;
            }
        }

        // 不可能进到这种情况,随便写了个数字
        return -1;
    }

    public static void main(String[] args) {
   

        int[] people = new int[7];      // 7个人的最后结果是4
        Arrays.fill(people, 1);    // 对数组中的所有元素,初始时,都有人,填充为1.
        int resultIndex = process(people);
        System.out.println(resultIndex);        // 4

    }


}

链表模拟

/** * 用链表模拟解决。(是一个循环链表) */
public class JosephusProblemByList {
   
    // 内部类,节点的定义。
    private static class Node{
   
        int value;
        Node next;

        public Node(int value) {
   
            this.value = value;
            this.next = null;
        }

        // 一定不能重写 toString,否则会 栈溢出异常。
    }


    /** * @param head 循环链表的头节点 * @param m 报数到m就出局 * @return 最后留下来的节点。 */
    public static Node getLiveNode(Node head, int m) {
   
        // 传入的参数不符合实际,直接返回null。
        if (head == null || m < 1 || head.next == null) {
   
            return null;
        }

        // 找到 链表中的最后一个节点last
        Node last = head;
        while (last.next != head) {
   
            last = last.next;
        }

        // 模拟操作,需要用到 第一个、最后一个节点 来维护循环链表的结构。
        // 只剩下一个节点的时候就停止。
        int count = 0;      // 记录报数的变化
        while (last != head) {
   
            count++;
            if (count == m) {
         // 报数为m的节点,就去除掉了。
                last.next = head.next;
                head = last.next;
                count = 0;  // 重置记录报数的变量
            } else {
   
                // 其他的报数情况,平安的节点。
                // 只要把 last、head变量往下移动一位即可。维护链表。
                last = last.next;
                head = last.next;
            }
        }


        // 返回仅剩的最后一个节点。
        return head;
    }

    public static void main(String[] args) {
   
        // 测试用例1: 7个节点的循环链表,最后留下来的是 4节点。
        Node node = new Node(1);
        node.next = new Node(2);
        node.next.next = new Node(3);
        node.next.next.next = new Node(4);
        node.next.next.next.next = new Node(5);
        node.next.next.next.next.next = new Node(6);
        node.next.next.next.next.next.next = new Node(7);

        System.out.println(getLiveNode(node, 3).value);     // 4

    }
}