约瑟夫环问题

不想看解析的我用的递归方法

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

据说在古罗马时期,犹太人士兵为了不被罗马人俘虏,决定集体自杀,他们会围成一个圆第一个人杀掉第二个人,第三个人杀掉第四个人,第五个人杀掉第六个人,以此类推,直到只剩一个人,他再自杀.
而在犹太人中,有个胆小鬼叫约瑟夫,他想活下来,可是为了活下来他就必须成为最后一个人,因此站在那里对他至关重要,我称之为胜利位
当人数少时我们很用以得出结论,比如
有1人,1是胜利位.
有2人,1杀掉2,1是胜利位.
有3人,1杀掉2,3杀掉1,3就是胜利位.
有4人,1杀掉2,3杀掉4,1杀掉3,1是胜利位.
有5人,1杀掉2,3杀掉4,5杀掉1,3杀掉5,3是胜利位

我们做一张表左列表示总人数,右列表示胜利位
在这里插入图片描述
通过观察表,我们发现胜利位都是奇数,这很好理解,因为在第一圈中奇数士兵总能杀掉偶数士兵.因此可以排除所有的偶数位置了,
更重要的是,我们发现 1 这个位置频繁出现,而他们出现的对应总人数为 1,2,4,8,此时敏感的你应该已经发现问题,这不是2^n吗.

为了方便理解,接下来我画图解释
箭头代表杀掉,黑色代表这一轮的死人.
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
可以看到当把第一轮的死人去掉后,第二轮还是站在偶数位上的人死.因此不论是有多少人总数只要是2^n,都是1位上的人会活下来.

那么当人数不是2^n的情况怎么办呢?
比如11人时,我们可以表示为2^3+3那么就相当于多余了三人,我们在第一轮拿掉这三人

在这里插入图片描述
杀掉三个"多余人"
在这里插入图片描述
拿掉他们三个,从下一个人开始重新排序
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
经过对比,可知现在的1就是当初的7.
因此7就是胜利位.
推算出数学公式为 S=2X+1,n=2^Y+X

你可能会遇到约瑟夫环变种的问题,比如一群人围在一起从1开始报数,报道m的人淘汰,从下一个人再从1开始报数,这种问题是大同小异的.

如果使用暴力解法使用数组解决,思想是遍历数组当遇到m时就标记它.但这样做会非常麻烦,临界条件特别多,每次遍历到数组最后一个元素的时候,还得重新设置下标为 0,并且遍历的时候还得判断该元素时候是否是被标记.

有些人可能会采用双向链表的方法,不同于数组,当遍历到m时不是标记而是直接将它删除.

而我推荐在n不是特别大时,使用递归的方法.按照上面的思想开始找递归公式吧
第一次
1
2
3

m
m+1
m+2
m+3
进行一次报数后,总人数变为n-1,以前位置和现在位置的编号为
… === …
m-2=n-2
m-1
=n-1
m=不存在已被删除
m+1
=1
m+2=2
m+3
=3
可得递归方程为 old = (now + m - 1) % n + 1.
为什么不是 old = (now + m - 1) % n呢,是因为编号是从1开始的,否则now+m=n时,old会等于0.
代码

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

非常简洁,但一定记住只适用于n不是很大的数时,因为怕递归太深,超过栈容量.

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值