1、前言

约瑟夫环问题是算法中相当经典的一个问题,其问题理解是相当容易的,并且问题描述有非常多的版本,并且约瑟夫环问题还有很多变形,这篇约瑟夫问题的讲解,一定可以带你理解通通!

2、约瑟夫问题

  1. -- 问题:
  2. 0,1,···,n-1n个数字排成一个圆圈,从数字0开始,
  3. 每次从这个圆圈里删除第m个数字(删除后从下一个数字开始计数)。
  4. 求出这个圆圈里剩下的最后一个数字。
  5. -- 例如:
  6. 012345个数字组成一个圆圈,从数字0开始每次删除第3个数字,
  7. 则删除的前4个数字依次是2041,因此最后剩下的数字是3

当然,这里考虑m,n都是正常的数据范围,其中

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

对于这个问题,你可能脑海中有了印象,想着小时候村里一群孩子坐在一起,从某个开始报数然后数到几出列,下一个重新开始一直到最后一个。
咱们仔细思考:假设当前长度为n,数到第m个(通过上面分析可以求余让这个有效的m不大于n)删除,在index位置删除。那么删除后剩下的就是n-1长度,index位置就是表示第一个计数的位置,我们可以通过求余得知走下一个删除需要多少步,那么下个位置怎么确定呢?
1.6、约瑟夫问题 - 图1
你可以分类讨论看看走的次数是否越界,但这里有更巧妙的方法,可以直接求的下一次具体的位置,公式就是为:

dead_index=(index+(m-1))%(len(list));

因为index是从1计数,如果是循环的再往前m-1个就是真正的位置,但是这里可以先假设先将这个有序集合的长度扩大若干倍,然后从index计数开始找到假设不循环的位置index2,最后我们将这个位置index2%(集合长度)即为真正的长度。
1.6、约瑟夫问题 - 图2
使用这个公式一举几得,既能把上面m过大循环过多的情况解决,又能找到真实的位置,就是将这个环先假设成线性的然后再去找到真的位置,如果不理解的话可以再看看这个图:
1.6、约瑟夫问题 - 图3

3、实现代码

def solution(n, m):
    people = [i for i in range(1, n+1)]
    index = 0  #当前的index
    while len(people) > 0:
        dead_index = (index + (m-1)) % len(people) # 删除的index
        yield people.pop(dead_index)
        index = dead_index  # 当前的index

print(list(solution(9, 4)))