这是我参与11月更文挑战的第17天,活动详情查看:2021最后一次更文挑战
圆圈中最后剩下的数字
问题描述
剑指 Offer 62. 圆圈中最后剩下的数字
0,1,···,n-1这n个数字排成一个圆圈,从数字0开始,每次从这个圆圈里删除第m个数字(删除后从下一个数字开始计数)。求出这个圆圈里剩下的最后一个数字。
例如,0、1、2、3、4这5个数字组成一个圆圈,从数字0开始每次删除第3个数字,则删除的前4个数字依次是2、0、4、1,因此最后剩下的数字是3。
示例:
输入:n = 5, m = 3
输出:3
分析问题
问题来历
犹太历史学家Josephus有过以下的故事:在罗马人占领乔塔帕特后,39 个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。然而Josephus 和他的朋友并不想遵从。首先从一个人开始,越过k-2个人(因为第一个人已经被越过),并杀掉第k个人。接着,再越过k-1个人,并杀掉第k个人。这个过程沿着圆圈一直进行,直到最终只剩下一个人留下,这个人就可以继续活着。问题是,给定了和,一开始要站在什么地方才能避免被处决。Josephus要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏。-----《约瑟夫问题》百度百科
求解
这道题其实是一个数学问题。首先我们定义函数f(n,m),表示在n个数字(序号0,1...,n-1)组成的圆圈中不断的删除第m个数字,最后剩下的那个数。开始时,在n个数字中删除第m个数字,则删除后就剩下n-1个数,那么再次删除就是在这个n-1个数中删除第m个数,可以用f(n-1,m)来表示,因此我们可以使用动态规划的方式来求解。
但是这里有一点需要注意。因为f(n,m)表示的是从0开始对区间0n-1不断的进行删除操作。第一步删除时,起点是0,区间是0n-1,被删除的元素是(m-1)% n,假设其等于k。那么第二步删除时,起点是k+1,区间也不满足上述要求,因此第二步不断删除的结果可以表示为f’(n-1,m)。则有f(n,m)=f’(n-1,m)。下面我们来看一下f’(n-1,m)和f(n-1,m)之间的关系。
我们假设f’(n-1,m)=y,f(n-1,m)=x。
通过观察,我们可以知道x和y的关系为y=(x+k+1)%n,所以f’(n-1,m)=(f(n-1,m)+k+1)%n=(f(n-1,m)+(m-1)%n+1)%n=(f(n-1)+m)%n。当n=1时,数组中只有一个数字0,因此有f(1,m)=0,由此我们可以得出递推表达式:
有了递推公式后,我们直接翻译成代码就好了。下面我们来看一下代码实现。
class Solution:
def lastRemaining(self, n, m):
x = 0
#从2开始遍历求解
for i in range(2, n + 1):
x = (x + m) % i
return x