前言:剑指offer刷题系列
问题:
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
思路1:
-
我首先想的是创建一个列表,叫做circle,它包含了从0到n-1的所有整数,用来模拟圆圈里的数字。
-
然后初始化一个变量
index = 0,表示当前要删除的数字的索引,初始时为0。 -
然后进行一个循环,条件是圆圈里还有多于一个数字。
-
index = (index + m - 1) % len(circle)这一行计算了要删除的数字的索引,由于每次删除后从下一个数字开始计数,所以要加上m-1,然后由于圆圈是循环的,所以要取模以防越界。 -
然后删除索引为index的数字,并更新circle列表。
-
最后返回圆圈里剩下的最后一个数字,即列表中的第一个元素。
时间复杂度:O(n*n)
空间复杂度:O(n)
基于上述思考,代码如下:
class Solution:
def lastRemaining(self, n: int, m: int) -> int:
circle = list(range(n))
index = 0
while len(circle) > 1:
index = (index + m - 1) % len(circle)
circle.pop(index)
return circle[0]
执行结果如下图:
时间复杂度太高了,测试通不过。
思路2:
- 首先判断n是否为1,如果是的话,那么最后剩下的数字就是0,直接返回。
return (self.lastRemaining(n-1, m) + m) % n这一行是核心的递归公式,它表示如果n大于1,那么可以先求出f(n-1, m)的值,也就是把第一个数字0删除后剩下的n-1个数字组成的圆圈里最后剩下的数字。然后根据公式f(n, m) = (f(n-1, m) + m) % n计算f(n, m)的值,也就是原来n个数字组成的圆圈里最后剩下的数字。
基于上述思考,代码如下:
class Solution:
def lastRemaining(self, n: int, m: int) -> int:
if n == 1:
return 0
return (self.lastRemaining(n-1, m) + m) % n
执行结果如下图:
学到的知识点:
约瑟夫环问题的历史背景和故事,以及它与数学家加斯帕的关系。