圆圈中最后剩下的数(约瑟夫环问题)
描述
每年六一儿童节,牛客都会准备一些小礼物和小游戏去看望孤儿院的孩子们。其中,有个游戏是这样的:首先,让 n 个小朋友们围成一个大圈,小朋友们的编号是0~n-1。然后,随机指定一个数 m ,让编号为0的小朋友开始报数。每次喊到 m-1 的那个小朋友要出列唱首歌,然后可以在礼品箱中任意的挑选礼物,并且不再回到圈中,从他的下一个小朋友开始,继续0… m-1报数…这样下去…直到剩下最后一个小朋友,可以不用表演,并且拿到牛客礼品,请你试着想下,哪个小朋友会得到这份礼品呢?
数据范围:1<= n <= 5000, 1<= m <= 10000
要求:空间复杂度 O(1),时间复杂度 O(n)
法一:通过链表或数组模拟
思路
通过数组来模拟循环列表,初始情况下给每个小朋友编号0~n-1,再用一个变量count来记录已经报数的个数,若count==m,则将该报数的小朋友编号记为-1,表示已经出局,不断循环,直到还剩下唯一一个编号不是-1的,返回该编号即可。 该方法虽然容易理解,却容易超时
时间复杂度:O(N^2)
空间复杂度:O(N),申请一个数组标记每个小孩子是否退圈
# -*- coding:utf-8 -*-
class Solution:
def LastRemaining_Solution(self, n, m):
array = list(range(n))
i = 0
count = 0 # 记录已经报数的
while array.count(-1) < n-1: # 若只剩一个小朋友,则跳出
if array[i] != -1:
count = count+1
if count==m:
array[i] =-1
count = 0
i = i+1
if i==n: # 数组模拟循环列表
i = i%n
# 遍历输出答案
for i in array:
if i!=-1:
return i
法二: 约瑟夫问题递推公式
思路: 利用约瑟夫问题的递推公式 f(n,m) = ( f(n-1,m) +m)%n )
公式递推:
令f(n,m)表示最后一个人的下标。
1.假设有n个人,报数m, 从0 开始报数,易知出圈的人下标为 m-1。
2.m-1 出圈后,我们对剩余人重新编号 即 第m个人下标为0,第m+1 下标为1 …以此编号。 那么重新编号之后,最后一个人的下标为f(n-1,m)
问题1: 在重新编号之后的f(n-1,m) 与 重新编号之前的f(n,m)有什么关系?
重新编号之前 m, m+1,m+2,…
重新编号之后 0 ,1 ,2,…
可知 (新编号+m)%n = 旧编号
即f(n,m) = (f(n-1,m)+m) %n;
递归写法复杂度分析:
时间复杂度: O(N)
空间复杂度: O(N)
class Solution:
def LastRemaining_Solution(self, n, m):
if n<=0:
return -1
return (self.LastRemaining_Solution(n-1,m)+m)%n
迭代方式:
class Solution:
def LastRemaining_Solution(self, n, m):
if n<=0:
return -1
f = 0
for i in range(2,n+1):
f = (f+m)%i
return f