约瑟夫环问题 —— 算法

前言

今天在浏览技术文章时看到的这么一个问题,感觉甚是有趣,专门来研究研究下,在浏览众多关于约瑟夫讲解的文章后,便进行一个较为详细的总结。

约瑟夫问题,是一个计算机科学和数学中的问题,在计算机编程的算法中,类似问题又称为约瑟夫环,又称“丢手绢问题”。约瑟夫问题在各大刷题网站有各种各样的变体,这里列举了一部分的问题描述,但不管怎么变,其意思都是一样的。

约瑟夫环问题一

0,1,…,n−1 这 n 个数字排成一个圆圈,从数字 0 开始,每次从这个圆圈里删除第 m 个数字。求出这个圆圈里剩下的最后一个数字。

例如,0、1、2、3、40、1、2、3、4 这 5 个数字组成一个圆圈,从数字 0 开始每次删除第 3 个数字,则删除的前 4 个数字依次是 2、0、4、12、0、4、1,因此最后剩下的数字是 3。

约瑟夫环问题二

一个由 n 个节点构成的数环,从某个点开始依次编号,然后每一轮剔除第 k 个节点,剩余的形成新的环,从被删除的节点的下一个开始计数,继续剔除,直至只剩下一个节点,求这个节点在最开始 n 个节点中是第几号。

约瑟夫环问题三

N个人围成一圈,第一个人从1开始报数,报M的将被杀掉,下一个人接着从1开始报。如此反复,最后剩下一个,求最后的胜利者。

约瑟夫环问题四

已知n个人(以编号1,2,3…n分别表示)围坐在一张圆桌周围。从编号为1的人开始报数,数到m的那个人出列;他的下一个人又从1开始报数,数到m的那个人又出列;依此规律重复下去,要求找到最后一个出列的人

约瑟夫环问题五

一只猫抓住了n只老鼠,其将老鼠排成一圈,依次按照1~m报数,报m值的吃掉,直到只剩下一只老鼠时,猫将其放生,求获生的老鼠编号。

约瑟夫环问题六

海盗船长抓到了n个俘虏,但是海盗船只能容许再多一个人,于是他决定让这些俘虏围成一个圈,然后1234的顺序报数,谁报到m就枪毙谁。那么这些俘虏谁可以存活下来呢?

约瑟夫环问题七

这个就是leetcode1823的题—— 找出游戏的获胜者。这7种描述,不管怎么变,万变不离其宗。对于上述各个问题描述中出现的m,因为m是不确定的,所以没有确切的公式推导,但若是m=2,就有准确的数学递归公式,详情请看约瑟夫环数学问题

约瑟夫环问题解决一 —— 模拟队列

对于约瑟夫环问题,我们可以采取模拟队列的方式,将n个元素一一进队,之后将队首元素出队加到队尾,依次加k-1次,第k次要删除的那个元素就是队首元素了,将其出队即可,循环往复,直至队中只剩最后一个元素。返回这个元素即可。

public static int findTheWinner(int n,int k){
        Queue<Integer> queue = new ArrayDeque<>();
        for (int i = 1; i <= n ; i++) {
            queue.offer(i);
        }
        while (queue.size() != 1){
            for (int i = 1; i < k ; i++) {
                queue.offer(queue.poll());
            }
            queue.poll();
        }
        return queue.peek();
    }

约瑟夫环问题解决二 —— 环形链表

对于约瑟夫环问题,我们还可以采用环形链表的方式来解决,构建一个长度为n的链表,各节点值为对应的索引,每轮删除第 m 个节点,直至链表长度为 1 时结束,返回最后剩余节点的值即可。

public static int findTheWinner(int n,int k){
        if (k==1){//按一个人计数,最后一个人就是赢家
            return n;
        }
        Node head = new Node(1);
        Node tmp = head;
        for (int i = 2;i<=n;i++){//建立长度为n的链表
            tmp.next = new Node(i);
            tmp = tmp.next;
        }
        tmp.next = head;//构成环
        tmp = head;
        int i = 0;
        while (tmp.val!=tmp.next.val){//当还有一个人的时候,值才会相等,退出循环
            i++;
            if (i==k-1){
                tmp.next = tmp.next.next;//每轮到第k个人,就kill掉
                i=0;//然后重新计数
            }
            tmp = tmp.next;
        }
        return tmp.val;
    }

约瑟夫环问题解决三 —— 动态规划

对于约瑟夫环问题,可以根据动态规划的方式解决,所谓动态规划就是把大问题分成小问题进行处理找到逻辑关系【递推公式】

  • 假定f(n,m)表示的是第一步从0开始对区间0~n-1不断进行删除操作。
  • 起点是0,区间是0~n-1,被删除的元素是(m-1)%n 用k表示。
  • 第二步删除时,起点是 k+1 ,结果可以表示为f(n-1,k)。
  • 根据问题规模有由变成n-1,前后位置差为k。
  • 递推得到f(n,k)= (f(n-1,k)+ k)% n

递归方式:

    public static int findTheWinner3(int n,int k){
        if (n<=1){
            return n;
        }
        int ans = (findTheWinner3(n-1,k)+k)%n;
        return  ans== 0? n:ans;
    }

采用迭代进行优化:时间复杂度O(n) ,空间复杂度O(1)

public static int findTheWinner2(int n,int k){
        int pos = 0;
        for (int i = 2; i < n+1; i++) {
            pos = (pos + k)%i;
        }
        return pos+1;
    }
  • 3
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

༄༊心灵骇客༣

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值