剑指 Offer 62. 圆圈中最后剩下的数字

104 阅读2分钟

前言:剑指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]

执行结果如下图:

image-20230912233556665.png

时间复杂度太高了,测试通不过。

思路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

执行结果如下图:

image-20230912234018984.png

学到的知识点:

约瑟夫环问题的历史背景和故事,以及它与数学家加斯帕的关系。