搬运一下自己b站发的文章
约瑟夫环自写算法的思路—1 - 哔哩哔哩 (bilibili.com)一种时间复杂度小于O(n)的约瑟夫环问题算法 - 哔哩哔哩 (bilibili.com)
约瑟夫环问题简介:
已知 n 个人(以编号1,2,3…n分别表示)围坐在一张圆桌周围。从编号为 1 的人开始报数,数到 m 的那个人出圈;他的下一个人又从 1 开始报数,数到 m 的那个人又出圈;依此规律重复下去,直到剩余最后一个人。
解决思路部分:
直接上python代码,复杂度log(n):
#注:编号是从1开始记的,和力扣上从0开始记的编号不同!
def deadeline(num,d,cold):
#不能优化的情况,直接用正常方法处理
if num<=d:
W=list(range(1,num+1))
P=[]
while len(W):
for i in range(d-1):
W.append(W.pop(0))#pop(0) 从列表中移除第一个元素并返回该元素。append() 将返回的元素添加到列表的末尾。
P.append(W[0])
W.pop(0)
return(P[cold-1])
#准备生成递推表,用于确定递推迭代每步操作
number=num
list1=[] #初始化每步对应的余数列表
list2=[] #初始化每步对应的n值列表
while number!=d: #当n值仍然大于d,则继续计算递推表
list2.append(number) #记下这步对应的n值
list1.append(number%d) #记下这步对应的余数
if number%d:
number-=number%d #如果不能整除d,减去模d的余数
else:
number-=number//d #如果整除d,减去这个数除d后的数
#初始化S一开始的赋值
cold=num-cold+1 #P之后要倒序操作,这里cold也需要倒序
if cold<=d: #获得在d个人时的所有死亡顺序
W=list(range(1,d+1)) #初始化还没死的人
P=[] #用于记录死亡顺序
while len(W): #pop(0) 从列表中移除第一个元素并返回该元素。append() 将返回的元素添加到列表的末尾。
for i in range(d-1):
W.append(W.pop(0))
P.append(W[0])
W.pop(0)
P=P[::-1] #P这里可以正序,倒序是因为迭代所有死亡顺序时更方便,这里沿用了该操作
S=P[cold-1] #获得S在d个人时对应的编号
else:
S=0 #S在d个人时没有S对应的编号,赋值为0表示为待赋值。
Q=d #人数
le=d #临时人数
#开始迭代S的值
for i in range (len(list1)-1,-1,-1): #递归结构体
if list1[i]: #余数不为0时,多次迁移递归
for k in range (0,list1[i]): #余数list1[i]表示添加了多少人,也就要迁移遍历多少次
if cold<=Q: #cold<=Q时,S才被赋值过编号
le=list2[i]-list1[i]+k+1 #计算临时人数
S=(S+d)%(le) #迭代编号
if S==0:
S=le #矫正编号
Q+=1 #人数更新
if cold<=Q and S==0: #cold==Q时,S需要赋值编号
S=d
else: #余数为0时,进行倍增递归
if cold<=Q: #cold<=Q时,S才被赋值过编号
if S%(d-1)!=0: #迭代编号
S+=int((S-S%(d-1))/(d-1))
else:
S+=int((S)/(d-1)-1)
Q+=int(Q/(d-1)) #人数更新
if cold<=Q and S==0: #cold==Q时,S需要赋值编号
S=3*(Q-cold+1)
return(S)
for i in range(1,42): #测试
print(deadeline(41,3,i),end = " ")
力扣上跑的验证视频: