1.题目
每年六一儿童节,牛客都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此。HF作为牛客的资深元老,自然也准备了一些小游戏。其中,有个游戏是这样的:首先,让小朋友们围成一个大圈。然后,他随机指定一个数m,让编号为0的小朋友开始报数。每次喊到m-1的那个小朋友要出列唱首歌,然后可以在礼品箱中任意的挑选礼物,并且不再回到圈中,从他的下一个小朋友开始,继续0…m-1报数…这样下去…直到剩下最后一个小朋友,可以不用表演,并且拿到牛客名贵的“名侦探柯南”典藏版(名额有限哦!!_)。请你试着想下,哪个小朋友会得到这份礼品呢?(注:小朋友的编号是从0到n-1)
如果没有小朋友,请返回-1
2.我的题解
2.1 模拟法
- 使用两重循环模拟数数的过程;
- 外部循环代表出局小朋友的个数,共出局
n-1
个,留最后一个; - 内部循环代表从
0
数到m-1
的过程,数到m-1
,该小朋友出局; - 出局的小朋友被标记,数到该小朋友的时候直接跳过;
- 最后还需要遍历一遍数组来找到最后留下的小朋友;
- 空间复杂度:多次访问数组,数数的过程中更会有多次无效访问,估计是
O(mn)
;
class Solution {
public:
int LastRemaining_Solution(int n, int m)
{
if (n<1 || m<1)return -1;
vector<int> isvisited(n, 0);
int start = 0, cnt = 0;
for (int i = 0; i<n - 1; i++) {//n-1 boys out
int j = start;
while (cnt<m) {//find the lucky boy
if (!isvisited[j])
cnt = cnt + 1;
j = (j + 1) % n;
}
isvisited[(j-1+n)%n] = 1;
start = j;cnt = 0;//next
}
for (int i = 0; i < n; i++)
if (!isvisited[i])return i;
return -1;
}
};
3.别人的题解
3.1 公式推导法
- 假设第一轮有n个人,数m个数,下标从0起;
- 第一局出局的人是
(m-1)%n
; - 第二局开始的人在第一局中位置为
m%n
; - 第二局开始的人在第二局中位置为
0
; - 第二局出局的人在第二局中位置为
(m-1)%(n-1)
; - 第二局出局的人在第一局中位置为
((m-1)%(n-1)+m)%m
; - 一般地,设
dp[n]
为n
人的情况下最后剩下的人(在n
人圈里的序号),dp[n-1]
为n-1
人的情况下最后剩下的人(在n-1
人圈里的序号),有公式:dp[n]=(dp[n-1]+m)%n
; dp[1]=0
;- 求解过程就是从最后一局(仅有一人)倒推的过程;
假设k=(m-1)%n
是n
个人情况下第一局出局的人,且将第二局时小朋友重新编号,详细关系如下表
第一局结束时的编号(x) | 第二局开始时的重编号(xx) | 映射关系 |
---|---|---|
k+1 k+2 …… k-2 k-1 k(已出局) | 0 1 2 …… n-2 n-1(已出局) | xx=(x-k-1+n)%n x=(xx+k+1)%n k=(m-1)%n x=(xx+m)%n |
class Solution {
public:
int LastRemaining_Solution(int n, int m)
{
if (n<1 || m<1)return -1;
int res=0;
for(int i=2;i<=n;i++)
res=(res+m)%i;
return res;
}
};
3.2 链表法
- 使用
vector
(或数组、链表(java)等有序、提供删除指定索引处元素函数的容器)模拟游戏过程,直到仅剩下一个 元素;
class Solution {
public:
int LastRemaining_Solution(int n, int m)
{
if (n<1 || m<1)return -1;
vector<int> vec;
for (int i = 0; i < n; i++)vec.push_back(i);
int start = 0;
while (vec.size() > 1) {
start = (start + m - 1) % vec.size();
vec.erase(vec.begin()+start);
}
return vec[0];
}
};
4.总结与反思
(1)约瑟夫环的站位;