描述
每年六一儿童节,牛客都会准备一些小礼物和小游戏去看望孤儿院的孩子们。其中,有个游戏是这样的:首先,让 n 个小朋友们围成一个大圈,小朋友们的编号是0 ~ n - 1。然后,随机指定一个数 m ,让编号为 0 的小朋友开始报数。每次喊到 m - 1 的那个小朋友要出列唱首歌,然后可以在礼品箱中任意的挑选礼物,并且不再回到圈中,从他的下一个小朋友开始,继续0 … m - 1报数…这样下去…直到剩下最后一个小朋友,可以不用表演,并且拿到牛客礼品,请你试着想下,哪个小朋友会得到这份礼品呢?
示例1
输入:5,3
返回值:3
示例2
输入:2,3
返回值:1
说明:有2个小朋友编号为0,1,第一次报数报到3的是0号小朋友,0号小朋友出圈,1号小朋友得到礼物
示例3
输入:10,17
返回值:2
方法一:递归(推荐使用)
知识点:递归
递归是一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解。因此递归过程,最重要的就是查看能不能讲原本的问题分解为更小的子问题,这是使用递归的关键。
思路:
n 个数相后去掉第m 个数,还剩下 n − 1 个数,依然要继续去掉第 m 个数。由此,从**(n,m)** 的问题变成了**(n−1,m)** 的子问题,其中若是**(n−1,m)** 的子问题返回的最后一个数是x ,则 (n,m) 返回的结果就是 (m+x)%n ,因此,用递归解决。
终止条件: 当 n = 1 时就只剩下了最后一个孩子,应该返回0.
返回值: 子问题的结果加上 m 再对 n 取模,就是上一级删除的元素。
本级任务: 递归进入子问题获取子问题删除的元素,再推算自己这一级删除的元素。
具体做法:
- step 1:首先判断没有小朋友的特殊情况。
- step 2:然后递归计算子问题,并将子问题的结果 x 运算**(m+x)%n** 得到父问题的结果。
图示:
代码:
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);
}
};
运行时间:4ms
超过53.37% 用C++提交的代码
占用内存:552KB
超过54.46%用C++提交的代码
复杂度分析:
时间复杂度: O(n),每个元素访问一次
空间复杂度: O(n),递归栈最大深度
方法二:迭代(扩展思路)
思路:
方法一的递归也我们可以用迭代来代替,递归是自顶向下找子问题,根据子问题再自顶向上返回给父问题,来推算父问题的结果。我们可以直接从小的子问题,往上推,还是根据公式**(m+x)%n** ,只是这里的 n变成了每一级子问题的长度。
for (int i = 2; i <= n; i++)
x = (m + x) % i;
具体做法:
- step 1:首先判断没有小朋友的特殊情况。
- step 2:假设最后剩余1个的时候,结果肯定为0.
- step 3:从最后剩余1个小朋友推断2个,再不断根据公式推断到 n个。
代码:
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;
}
};
运行时间:3ms
超过66.95% 用C++提交的代码
占用内存:524KB
超过59.07%用C++提交的代码
复杂度分析:
时间复杂度: O(n),一次遍历
空间复杂度: O(1),常数个变量,无额外辅助空间
官方解释~~~