校招算法笔面试 | 孩子们的游戏(圆圈中最后剩下的数)

题目

题目链接

题目的主要信息:
  • 从0到n-1(首尾相接)中每次去掉第m个数,下一次从去掉数的下一个开始,直到剩下最后一个数
  • 返回的是最后一个数
  • 有数为0的特殊情况
举一反三:

学习完本题的思路你可以解决类似的模拟问题。

方法一:递归(推荐使用)

知识点:递归

递归是一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解。因此递归过程,最重要的就是查看能不能讲原本的问题分解为更小的子问题,这是使用递归的关键。

思路:

n n n个数相后去掉第 m m m个数,还剩下 n − 1 n-1 n1个数,依然要继续去掉第 m m m个数。由此,从 ( n , m ) (n,m) (n,m)的问题变成了 ( n − 1 , m ) (n-1,m) (n1,m)的子问题,其中若是 ( n − 1 , m ) (n-1,m) (n1,m)的子问题返回的最后一个数是 x x x,则 ( n , m ) (n,m) (n,m)返回的结果就是 ( m + x ) % n (m+x)\%n (m+x)%n,因此,用递归解决。

  • 终止条件: n = 1 n=1 n=1时就只剩下了最后一个孩子,应该返回0.
  • 返回值: 子问题的结果加上 m m m再对 n n n取模,就是上一级删除的元素。
  • 本级任务: 递归进入子问题获取子问题删除的元素,再推算自己这一级删除的元素。

具体做法:

  • step 1:首先判断没有小朋友的特殊情况。
  • step 2:然后递归计算子问题,并将子问题的结果 x x x运算 ( m + x ) % n (m+x)\%n (m+x)%n得到父问题的结果。

图示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Java实现代码:

public class Solution {
    private int function(int n, int m) {
        if (n == 1)  
            return 0;
        //递归
        int x = function(n - 1, m);
        //返回最后删除的那个元素
        return (m + x) % n;  
    }
    public int LastRemaining_Solution(int n, int m) {
        //没有小朋友的情况
        if(n == 0 || m == 0) 
            return -1;
        return function(n, m);
    }
}

C++实现代码:

class Solution {
public:
    int function(int n, int m) {
        if (n == 1)  
            return 0;
        //递归
        int x = function(n - 1, m);
        //返回最后删除的那个元素
        return (m + x) % n;  
    }
    int LastRemaining_Solution(int n, int m) {
        //没有小朋友的情况
        if(n == 0 || m == 0) 
            return -1;
        return function(n, m);
    }
};

Python实现代码:

import sys
sys.setrecursionlimit(100000) 
class Solution:
    def function(self, n: int, m: int) -> int:
        if n == 1:  
            return 0
        #递归
        x = self.function(n - 1, m)
        #返回最后删除的那个元素
        return (m + x) % n  
    
    def LastRemaining_Solution(self , n: int, m: int) -> int:
        #没有小朋友的情况
        if n == 0 or m == 0: 
            return -1
        return self.function(n, m)

复杂度分析:

  • 时间复杂度: O ( n ) O(n) O(n),每个元素访问一次
  • 空间复杂度: O ( n ) O(n) O(n),递归栈最大深度
方法二:迭代(扩展思路)

思路:

方法一的递归也我们可以用迭代来代替,递归是自顶向下找子问题,根据子问题再自顶向上返回给父问题,来推算父问题的结果。我们可以直接从小的子问题,往上推,还是根据公式 ( m + x ) % n (m+x)\%n (m+x)%n,只是这里的 n n n变成了每一级子问题的长度。

for (int i = 2; i <= n; i++)
    x = (m + x) % i;

具体做法:

  • step 1:首先判断没有小朋友的特殊情况。
  • step 2:假设最后剩余1个的时候,结果肯定为0.
  • step 3:从最后剩余1个小朋友推断2个,再不断根据公式推断到 n n n个。

Java实现代码:

public class Solution {
    public int LastRemaining_Solution(int n, int m) {
        //没有小朋友的情况
        if(n == 0 || m == 0) 
            return -1;
        int x = 0;
        //从小到大,更新x
        for(int i = 2; i <= n; i++)
            x = (m + x) % i;
        return x;
    }
}

C++实现代码:

class Solution {
public:
    int LastRemaining_Solution(int n, int m) {
        //没有小朋友的情况
        if(n == 0 || m == 0) 
            return -1;
        int x = 0;
        //从小到大,更新x
        for(int i = 2; i <= n; i++)
            x = (m + x) % i;
        return x;
    }
};

Python实现代码:

class Solution:
    def LastRemaining_Solution(self , n: int, m: int) -> int:
        #没有小朋友的情况
        if n == 0 or m == 0: 
            return -1
        x = 0
        #从小到大,更新x
        for i in range(2, n + 1):
            x = (m + x) % i
        return x

复杂度分析:

  • 时间复杂度: O ( n ) O(n) O(n),一次遍历
  • 空间复杂度: O ( 1 ) O(1) O(1),常数个变量,无额外辅助空间
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值