剑指offer--圆圈中最后剩下的数字(约瑟夫环的问题)

题目:

0,1,···,n-1这n个数字排成一个圆圈,从数字0开始,每次从这个圆圈里删除第m个数字(删除后从下一个数字开始计数)。求出这个圆圈里剩下的最后一个数字。

例如,0、1、2、3、4这5个数字组成一个圆圈,从数字0开始每次删除第3个数字,则删除的前4个数字依次是2、0、4、1,因此最后剩下的数字是3。

示例 1:

输入: n = 5, m = 3
输出: 3

思路:写这道题大概碰到了三个思路,其中第一个不够完善,情况少了一种没有分析清楚

思路一:将删除数字前面的数字切下来,拼接到最后,实现一个循环的列表。(只适用于m小于n的情况)

代码(未修改):

class Solution:
    def lastRemaining(self, n: int, m: int) -> int:
        #一种方法不取模,通过将要删除元素前面的元素移到列表的最后面,实现真正的循环
        nums=[i for i in range(0,n)]
        if m==1:return nums[-1]
        while len(nums)>=m:
            nums=nums[m:]+nums[0:m-1]
        nums=nums*2#防止列表中只有一种情况
        return nums[1]

代码(增加过条件判断):

class Solution:
    def lastRemaining(self, n: int, m: int) -> int:
        #一种方法不取模,通过将要删除元素前面的元素移到列表的最后面,实现真正的循环
        nums=[i for i in range(0,n)]
        if m==1:return nums[-1]
        temp=m
        while True:
            #不管m大小都先取余,然后判断取余之后的结果
            m=temp%len(nums)
            #m为0的时候是特殊情况,剩下的情况都是直接切片加拼接,直到列表中只剩下最后 一个元素
            if m==0:
                nums.pop(-1)
            else:
                nums=nums[m:]+nums[0:m-1]
            if len(nums)==1:
                return nums[0]    

思路二:通过更换索引的方法,来模拟整个删除的过程,但是最后代码运行超时。

假设初始的index=0,每隔m步删掉一个元素,那index=(index+m-1)%len(nums),通过不停的对当前的列表长度取模,index每次加上m步,实现每次的index都是要删除的那个元素。

代码:

class Solution:
    def lastRemaining(self, n: int, m: int) -> int:
        #另一种更换索引的方法,结果都正确,模拟循环,但是超时
        nums=[i for i in range(0,n)]
        index=0
        for i in range(n-1):
            index=(index+m-1)%len(nums)
            del nums[index]
            if len(nums)==1:
                return nums[0]

思路三:动态规划的方法,通过索引位的数学关系推出来上一步中最后留下的这个元素的索引位。

最后留下的元素w索引肯定是0,那还剩下两个元素的时候,索引位index(上一次删除元素之前w的索引位)=(index+m)%i(当前还剩下i位元素),直到递推出来初始元素数组中的位置索引,那就是我们要找的元素(因为这题中我们设置的索引位和元素值是相同的,所以直接用索引位来表示元素),每次加上m我们可以理解为每次删除一个元素后,我们最终值w都要向前移动,因为每次删除一个元素,该元素的前面的几个元素就相当于是被切到了整个数组的最后,所以向前移动个m步,我们要做的就是一步一步的移回来。

举例理解:

'''

        最后只剩下一个元素,假设这个最后存活的元素为 num, 这个元素最终的的下标一定是0 (因为最后只剩这一个元素),

        所以如果我们可以推出上一轮次中这个num的下标,然后根据上一轮num的下标推断出上上一轮num的下标,

        直到推断出元素个数为n的那一轮num的下标,那我们就可以根据这个下标获取到最终的元素了。推断过程如下:

        首先最后一轮中num的下标一定是0, 这个是已知的。

        那上一轮应该是有两个元素,此轮次中 num 的下标为 (0 + m)%n = (0+3)%2 = 1; 说明这一轮删除之前num的下标为1;

        再上一轮应该有3个元素,此轮次中 num 的下标为 (1+3)%3 = 1;说明这一轮某元素被删除之前num的下标为1;

        再上一轮应该有4个元素,此轮次中 num 的下标为 (1+3)%4 = 0;说明这一轮某元素被删除之前num的下标为0;

        再上一轮应该有5个元素,此轮次中 num 的下标为 (0+3)%5 = 3;说明这一轮某元素被删除之前num的下标为3;

        ...

        因为我们要删除的序列为0-n-1, 所以求得下标其实就是求得了最终的结果。比如当n 为5的时候,num的初始下标为3,

        所以num就是3,也就是说从0-n-1的序列中, 经过n-1轮的淘汰,3这个元素最终存活下来了,也是最终的结果。

        总结一下推导公式:(此轮过后的num下标 + m) % 上轮元素个数 = 上轮num的下标

代码:

class Solution:
    def lastRemaining(self, n: int, m: int) -> int:
        #(此轮过后的num下标 + m) % 上轮元素个数 = 上轮num的下标
        index=0
        for i in range(2,n+1):
            index=(index+m)%i
        return index

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值