约瑟夫环——公式法——附LeetCode—剑指offer题目—剑指 Offer 62. 圆圈中最后剩下的数字

约瑟夫环问题:

       约瑟夫问题是个著名的数学问题,N个人围成一圈,从第一个人开始数,杀掉第M个人。然后从第M+1个人再开始数,数到第M个人再杀掉,然后再从第M+1个人开始数,数到第M个人再次杀掉,反复进行,直到这圈人只剩下最后一个人。游戏结束。

那么问题来了,如何快速的判断最后留下的那个人是谁呢?这就是约瑟夫环要研究的问题。

我们以数组为例研究,假设 N=8 , M=3,     F(8 , 3) = 7     (这个7是 7这个人的序号)

                                1      ,       2 ,       3 ,       4 ,       5 ,       6 ,       7,       8

 第一个回合:杀掉第三个人数组变成    F(7 , 3) = 4  (这个1是 7这个人的序号)

                                        4 ,       5 ,       6 ,       7,       8,       1,       2

 第二个回合:杀掉第三个人数组变成     F(6 , 3) = 1  (这个5是 7这个人的序号)

                                              7,       8,       1,       2,       4,       5

第三个回合:杀掉第三个人数组变成       F(5 , 3) = 4 在  (这个2是 7这个人的序号)

                                                  2,       4,       5,       7,       8

第四个回合:杀掉第三个人数组变成        F(4 , 3) = 1   (这个4是 7这个人的序号)

                                                      7,       8,       2,       4

第五个回合:杀掉第三个人数组变成        F(3 , 3) = 2   (这个1是 7这个人的序号)

                                                        4,       7,       8

第六个回合:杀掉第三个人数组变成(因为是个环,所以数完7是第二个时候,就重新回到4数作为第三个,因此下一个是删掉的4)   F(2 , 3) = 2 (这个1是 7这个人的序号)

                                                             4,       7

第七个回合:杀掉第三个人数组变成    F(1 , 3) = 1 (这个1是 7这个人的序号)

                                                                  7

我们可以看到最后剩下的是7号,暴力的办法是模拟整个过程,然后得到最终留下的人,但是在程序里这样做会导致时间复杂度非常高,具体的可以看我的下面python的暴力程序。

约瑟夫环的方法类似于找个规律,让我们可以快速的确定那个人可以留到最后,我们可以看到

F(8 , 3) = 7    F(7 , 3) = 4 , F(8 , 3) =  F(7 , 3) + 3,因为每次杀掉第三个人之后,前两位也位移到数组最后面了,所以7号人的位置会左移三位,由7移动到4,相当于所在的下标 -3  。

这就是我们找到的规律,如果我们使用(倒推思想)让最后的一个函数 F(1 , 3)不停的+3 ,是不是就可以很快找到活到最后的人。 

那么我们来这样做,由上面可知 F(1 , 3)  = 1, 那么按照我们找到的规律可得

                                                     F(2 , 3) = F(1 , 3)  + 3 = 1 + 3 = 4

但是我们发现F(2 , 3)的列表长度一共才2,序号4从何谈起呢?因此我们需要对我们每次的+3操作再加上一个操作,即对当前列表的长度进行取余。等式变成如下

                                     F(2 , 3) = (F(1 , 3)  + 3)%2 = (1 + 3 )% 2 =  4%2 = 2

因为每次移动到最后之后如果数组没有了,但是还没移动完,就回到第一个重新移动,这就是取余的原因,因为我们这个是个围成圈的嘛!

                                    F(3 , 3) = (F(2 , 3)  + 3)% 3 = (2 + 3 )% 3 =  5%3 = 2

                                    F(4 , 3) = (F(3 , 3)  + 3)% 4 = (2 + 3 )% 4 =  5%4 = 1

                                    F(5 , 3) = (F(4 , 3)  + 3)% 5 = (1 + 3 )% 5 =  5%5= 4

                                    F(6 , 3) = (F(5 , 3)  + 3)% 6 = (4 + 3 )% 6 =  7%6 = 1

                                    F(7 , 3) = (F(6 , 3)  + 3)% 7 = (1 + 3 )% 7 =  4%7 = 4

                                    F(8 , 3) = (F(7 , 3)  + 3)% 8 = (4 + 3 )% 8 =  7%8 = 7

 我想到这里,这个规律我们已经看的很清楚了,即

                                       F(N , M) = (F(N-1 , M)  + M)%N

没错,这个公式就是我们的约瑟夫环的递推公司,用这个方法求解下面的剑指offer的习题即可

 

 

剑指 Offer 62. 圆圈中最后剩下的数字

难度简单610收藏分享切换为英文接收动态反馈

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

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

示例 1:

输入: n = 5, m = 3
输出: 3

示例 2:

输入: n = 10, m = 17
输出: 2

限制:

  • 1 <= n <= 10^5
  • 1 <= m <= 10^6

通过次数148,821提交次数226,172

第一种暴力法:

构造队列,deque,rotate函数可以旋转队列,使它真的变成圈
每次计算余数,然后进行队列将m之前的数放到队列最后,然后弹出最左边的数

时间有点长 

 

Python代码:

class Solution:
    def lastRemaining(self, n: int, m: int) -> int:
        ans = collections.deque(list(range(n)))
        while len(ans)!=1:
                ans.rotate(-(m%len(ans))+1)#队列左移
                ans.popleft()  #移动完之后,弹出
        return ans[0]
        
        
            
            

第二种约瑟夫环法-递归思想代码:

Python代码:

class Solution:
    def lastRemaining(self, n: int, m: int) -> int:
        f = 0
        for i in range(2 , n+1): f = (f+m)%i
        return f

  C++代码:

class Solution {
public:
    int lastRemaining(int n, int m) {
        int f=0;
        for(int i=2; i<n+1; i++) f = (f+m)%i;
        return f;

    }
};

 

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

小馨馨的小翟

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

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

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

打赏作者

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

抵扣说明:

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

余额充值