孩子们的游戏
题目
每年六一儿童节,牛客都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此。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,递归结束,得到最后胜利者。但是我们要记住,子问题的结果都是父问题为了使得问题逻辑一样而改变了编号值,所以我们要向上不断恢复编号值!
这个问题其实是需要找规律的,并且将一个复杂的问题推导成一个简单的数学公式。现在我们假设有一个函数 f ( n , m ) f(n,m) f(n,m),这个函数代表的是有 n − 1 n-1 n−1个人,从 0 , 1 , 2... , n − 1 0,1,2...,n-1 0,1,2...,n−1开始往后查,找到第 m m m个数删除,然后再往后找,最终剩下的数字。
在这 n n n个数字中,第一个被删除是 ( m − 1 ) % n (m-1)\%n (m−1)%n,这个是取余,也叫做取mod, ( m − 1 ) / n (m-1)/n (m−1)/n的余数,被删除的这个数字我们记为k,那么k之后剩下的n-1个数字为 0 , 1 , 2 , . . . , k − 1 , k + 1 , . . . , n − 1 0,1,2,...,k-1,k+1,...,n-1 0,1,2,...,k−1,k+1,...,n−1,并且下一次开始从 k + 1 k+1 k+1计数。相当于在剩下的序列中 k + 1 k+1 k+1排在最前面,从而形成 k + 1 , . . . , n − 1 , 0 , 1 , . . . , k − 1 k+1,...,n-1,0,1,...,k-1 k+1,...,n−1,0,1,...,k−1.由于 n − 1 n-1 n−1这个序列的规律并不是从0开始计数,所以我们新定义一个函数 f ′ ( n − 1 , m ) f'(n-1,m) f′(n−1,m).如果我们能够找到这两个函数之间的关系,那我们就可以利用程序很简单的进行循环得到最终结果。
不难看出,无论是从n项找到最后的数字还是从n-1项(是原问题的子集)找到最后的数字,结果应该是一样的,因此有 f ( n , m ) = f ′ ( n − 1 , m ) f(n,m) = f'(n-1,m) f(n,m)=f′(n−1,m).接下来我们将通过一个简单的映射,将两个不同的函数组成的方程变为一个。
我们将这n-1组成的数字做成一个映射如下:
我们将映射定义为
p
(
x
)
=
(
x
−
k
−
1
)
%
n
p(x) = (x-k-1)\%n
p(x)=(x−k−1)%n,比如
x
=
k
+
1
x = k+1
x=k+1时,他的映射为0,依次类推(注意
−
2
%
n
=
n
−
2
-2\%n = n-2
−2%n=n−2).该映射的逆映射为
p
−
1
(
x
)
=
(
x
+
k
+
1
)
%
n
p^{-1}(x)=(x+k+1)\%n
p−1(x)=(x+k+1)%n。由于映射之后的序列与映射之前的序列具有同样的形式,都是从0开始,所以有:
f
′
(
n
−
1
,
m
)
=
p
−
1
(
f
(
n
−
1
,
m
)
)
=
[
f
(
n
−
1
,
m
)
+
k
+
1
]
%
n
.
f'(n-1,m)=p^{-1}(f(n-1,m))=[f(n-1,m)+k+1]\%n.
f′(n−1,m)=p−1(f(n−1,m))=[f(n−1,m)+k+1]%n.
所以现在就会有了一个关于f的递推公式。考虑边界条件,当n=1时,f返回的就是0,因此递推公式如下: f ( n , m ) = { 0 n = 1 [ f ( n − 1 , m ) + m ] % n n > 1 f(n, m)=\left\{\begin{array}{ll} 0 & n=1 \\ [f(n-1, m)+m] \% n & n>1 \end{array}\right. f(n,m)={0[f(n−1,m)+m]%nn=1n>1
之后就可以编程实现了!
# -*- 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