孩子们的游戏
题目
每年六一儿童节,牛客都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此。HF作为牛客的资深元老,自然也准备了一些小游戏。其中,有个游戏是这样的:首先,让小朋友们围成一个大圈。然后,他随机指定一个数m,让编号为0的小朋友开始报数。每次喊到m-1的那个小朋友要出列唱首歌,然后可以在礼品箱中任意的挑选礼物,并且不再回到圈中,从他的下一个小朋友开始,继续0…m-1报数…这样下去…直到剩下最后一个小朋友,可以不用表演,并且拿到牛客名贵的“名侦探柯南”典藏版(名额有限哦!!_)。请你试着想下,哪个小朋友会得到这份礼品呢?(注:小朋友的编号是从0到n-1)
如果没有小朋友,请返回-1
思路
先举一个简单的例子:对于0,1,2,3,4的问题,m = 2, 第一次删除的人编号为1,那么之后重新报号的人的编号为2,问题变为2,3,4,0,为了使得该子问题能够与父问题一致(报号都从0开始,这样就可以采用相同的逻辑处理问题,这是递归经常使用的),现在我们把他们的编号做一下转换:
2 –> 0
3 –> 1
4 –> 2
0 –> 3
这样子问题变为0,1,2,3中删除第m个人,因为问题与父问题一致,所以可以采用相同的逻辑继续求解,这样直到问题规模为1,递归结束,得到最后胜利者。但是我们要记住,子问题的结果都是父问题为了使得问题逻辑一样而改变了编号值,所以我们要向上不断恢复编号值!
这个问题其实是需要找规律的,并且将一个复杂的问题推导成一个简单的数学公式。现在我们假设有一个函数 ,这个函数代表的是有 个人,从 开始往后查,找到第 个数删除,然后再往后找,最终剩下的数字。
在这 个数字中,第一个被删除是 ,这个是取余,也叫做取mod, 的余数,被删除的这个数字我们记为k,那么k之后剩下的n-1个数字为 ,并且下一次开始从 计数。相当于在剩下的序列中 排在最前面,从而形成 .由于 这个序列的规律并不是从0开始计数,所以我们新定义一个函数 .如果我们能够找到这两个函数之间的关系,那我们就可以利用程序很简单的进行循环得到最终结果。
不难看出,无论是从n项找到最后的数字还是从n-1项(是原问题的子集)找到最后的数字,结果应该是一样的,因此有 .接下来我们将通过一个简单的映射,将两个不同的函数组成的方程变为一个。
我们将这n-1组成的数字做成一个映射如下:
我们将映射定义为
,比如
时,他的映射为0,依次类推(注意
).该映射的逆映射为
。由于映射之后的序列与映射之前的序列具有同样的形式,都是从0开始,所以有:
所以现在就会有了一个关于f的递推公式。考虑边界条件,当n=1时,f返回的就是0,因此递推公式如下:
之后就可以编程实现了!
# -*- coding:utf-8 -*-
class Solution:
def LastRemaining_Solution(self, n, m):
# write code here
# f(n)=[f(n-1)+m]%n
if n == 1:
return 0
if n == 0 or m == 0:
return -1
value = 0 # f(1)=0
for i in range(2, n+1):
ret = (value + m)% i
value = ret
return ret