约瑟夫环问题解释


  

引言:

约瑟夫问题(有时也称为约瑟夫斯置换,是一个计算机科学和数学中的问题。在计算机编程的算法中,类似问题又称为约瑟夫环。又称“丢手绢问题”.)

约瑟夫问题

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

  这个问题是在我刷剑指Offer时遇到的,看了很多大佬的解题,都是用数字在进行举例,看完还是有一些疑惑,直到看了这两篇文章(文章1文章2)。这里换了个角度举例,或许会更清晰一些,欢迎大家讨论,并不吝赐教!

问题转换

  既然约瑟夫问题就是用人来举例的,那我们也给每个人一个编号(索引值),每个人用字母代替。下面这个例子是N=8,m=6的例子。
  我们定义F(n,m)表示最后剩下那个人的索引号,因此我们只关系最后剩下来这个人的索引号的变化情况即可。(注意:该思路只关心最终活着那个人的序号变化
示意图
从8个人开始,每次杀掉一个人,去掉被杀的人,然后把杀掉那个人之后的第一个人作为开头重新编号:

  • 第一次F被杀掉,人数变为7,G作为下一轮的开头(最终活下来的A的编号从0变成2)
  • 第二次D被杀掉,人数变为6,E作为下一轮的开头(最终活下来的A的编号从2变成3)
  • 第三次C被杀掉,人数变为5,E作为下一轮的开头(最终活下来的A的编号从3变成3)
  • 以此类推,当值剩下A一个人时,他的编号必定为0!(这是重点,要圈起来

最后活着的人的编号反推

  我们现在知道了A的索引号的变化过程,那么我们反推一下从 N=7 到 N=8 的过程。如何才能将 N=7 的排列变回到 N=8 呢?我们先把被杀掉的 F 补充回来然后右移 m 个人,发现溢出了,再把溢出的补充在最前面,神奇了 经过这个操作就恢复了 N=8 的排列了。如下图所示:
反推示意图
  因此我们可推出递推公式非 f(8,6) = (f(7,3) + 6) % 8,进行推广泛化后可以得到 f(n,m) = (f(n-1,m) + m) % n。

递推公式的导出

  在以上的推导结果中再把 n=1 这个最初的情况加上,就能得到地推公式:递推公式

java代码实现

class Solution{
	public int lastRemain(int n, int m) {
    	int pos = 0;//最后活下来的那个人的初始位置(这里是反向推导)
        for (int i=2; i <= n; i++) {
        	pos = (pos + m) % i;//每次循环右移
        }
        return pos;
    }
}

文章作者: YangChongZhi
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 YangChongZhi !
评论
 上一篇
滑动窗口中位数(优先队列 + 延迟删除) 滑动窗口中位数(优先队列 + 延迟删除)
   引言: LeetCode中遇到的一道题,记录一下。转自 求滑动窗口中的中位数 问题定义  中位数是有序序列最中间的那个数。如果序列的长度是偶数,则没有最中间的数;此时中位数是最中间的两个数的平均数。给你一个数组 nums,有一个长度
2021-02-03
下一篇 
vim用户手册 vim用户手册
   引言: Vim是从 vi 发展出来的一个文本编辑器。代码补完、编译及错误跳转等方便编程的功能特别丰富,在程序员中被广泛使用。简单的来说, vi 是老式的字处理器,不过功能已经很齐全了,但是还是有可以进步的地方。 vim 则可以说是程序
2021-01-24
  目录