四种方式解决约瑟夫环问题(数组,链表,递归,迭代)

四种方式解决约瑟夫环问题(数组,链表,递归,迭代)

约瑟夫环问题:有 N个人围成一个圈,从第一个人开始报数,每次报到 m 的时候这个人就出圈,后面的人接着从 1 开始报数,直到最后一个人出圈。

1、求解每一个出圈的人组成的序号列表(数组,链表)

2、求解最后那个出圈人的序号(递归,迭代)

一、数组求解序号列表

	/**
     * 使用一个数组来记录每个人,数组下标 i 表示 第 i+1 个人
     * 第 i + 1个人在圈里则 a[i] = 1 ,否则等于 0
     * 时间复杂度是 O(n * m), 空间复杂度是 O(n);
     */
    public static List<Integer> getResult(int n, int m) {
        //每一个人用数组的一个元素表示,第 i + 1个人的下标为 i
        //a[i] = 1,第 i + 1 个人还在圈中
        int[] a = new int[n];
        for (int i = 0; i < n; i++) {
            a[i] = 1;
        }
        //初始时在圈中的人数
        int alive = n;
        //用来记录每次出圈的人序号的列表
        List<Integer> res = new LinkedList<>();
        //当前报数的人的下标
        int cur = 0;
        while (alive > 0) {
            //计数,当计数达到 sum == m时表示找到最后一个报数的人
            int sum = a[cur];
            while (sum < m) {
                cur = (cur + 1) % n;
                sum += a[cur];
            }
            //人数减一
            alive--;
            //当前位置的人出圈
            a[cur] = 0;
            res.add(cur + 1);
        }
        return res;
    }

二、链表求解序号列表

	/**
     * 链表方式解决约瑟夫环问题
     */
    static class ListNode {
        int val;
        ListNode next;

        public ListNode(int val) {
            this.val = val;
        }
    }

    /**
     * 使用一个循环链表来解决约瑟夫问题,每次找到最后报数节点的前一个节点,然后将报数的节点删除
     * 注意:在得到所有出圈元素后,循环链表的最后一个节点会自己引用自己,即 node.next = node,
     * 在使用引用计数对象死亡判断的GC算法中会导致这个对象不会被回收,造成内存泄露,所以在最后要将 node.next = null
     * 时间复杂度是 O(n * m), 空间复杂度是 O(n);
     */
    public static List<Integer> getResultByListNode(int n, int m) {
        List<Integer> res = new LinkedList<>();
        //使用一个循环链表将 n 个人存起来,每个节点的值就是这个人的序号:从 1 - n
        ListNode dummy = new ListNode(-1);
        ListNode cur = dummy;
        for (int i = 1; i <= n; i++) {
            cur.next = new ListNode(i);
            cur = cur.next;
        }
        cur.next = dummy.next;
        //当前在圈里的人数
        int alive = n;
        //当前报数的节点的前一个节点
        ListNode node = dummy;
        while (alive > 0) {
            int sum = 0;
            while (sum < m - 1) {
                node = node.next;
                sum++;
            }
            res.add(node.next.val);
            //删除这个节点
            node.next = node.next.next;
            alive--;
        }
        //最后一个元素的 node.next = node,循环引用自身,所以要断开引用
        node.next = null;
        return res;
    }

三、递归方式求解最后存活的人的序号

	//递归方式:计算得到最后报数的人的序号,范围在 1 - n
    //f(n,m)表示在 n 个人的情况下,每次报 m个数,最后存活人的序号
    //f(n-1,m)表示在 n - 1个人的情况下,每次报 m 个数,最后存活的人的序号
    //每次有人被移除圈时,就将剩余所有人进行重新编号,此时最后存活的那个人的序号也会变化
    //时间复杂度为:O(n),空间复杂度为O(n)
    public static int f(int n, int m) {
        //只有一个人的时候,最后存活的那个人的序号肯定是 1
        if (n == 1) return 1;
        else {
            //f(n,m)情况下所有人的序号:1,2,···,m-2,m-1,m,m+1,m+2,···,n
            //f(n-1,m)情况下所有人的序号:······,n-2,n-1,null,1,2,···
            //从f(n-1,m)推出f(n,m),就是f(n,m) = (f(n-1,m)+m-1)%n + 1
            //这里之所以要将 f(n,m) = (f(n-1,m)+m-1)%n + 1,而不是f(n,m) = (f(n-1,m)+m)%n
            //   是由于下标是从1开始的,不可能取余后为 0;如果下标从 0 开始则可以直接 f(n,m) = (f(n-1,m)+m)%n
            return (f(n - 1, m) + m - 1) % n + 1;
        }
    }

四、迭代方式求解最后存活的人的序号

	//迭代方式求解最后存活的人的序号
    public static int fd(int n, int m) {
        int res = 1;
        for (int i = 2; i <= n; i++) {
            //注意:这里取余的是每次新的元素个数,也就是 i 个元素
            //只有当有 2 个元素的时候,才需要进行计算,而且要一直计算到 n
            res = (res + m - 1) % i + 1;
        }
        return res;
    }

更多算法解析的github地址:https://github.com/yangminWFN/studynotes/tree/master/algorithm

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值