孩子们的游戏(圆圈中最后剩下的数):最直观的想法是,约瑟夫环。首先创建一个bool类型的大小为n的数组people用于存储当前序号是否出列,初始均未出列,故将其初始化为false,然后使用一个变量i表示当前的序号,其范围为0~n-1,初始为-1,使用一个变量s表示当前这一轮报数中报了几个数,其范围为0~m,初始为0,使用一个变量num表示出列人数,其范围为0~n,初始为0,使用一个变量res保存结果序号。首先采用环形方式统计当前序号,然后如果当前序号未出列则统计报数,接着如果当前这一轮已经报数m个,则标记出列以及出列个数并且重置报数s为0,最后如果当前出列人数等于n则记录结果序号并且退出循环,最后返回结果序号res即可。(模拟)
int LastRemaining_Solution(int n, int m)
{
// 创建people数组 用于统计哪一个人出列 初始均未出列
vector<bool> people(n,false);
// i表示当前在哪一个序号 范围为0~n-1
int i=-1;
// s用于统计这一轮已经报了几个数 范围为0~m
int s=0;
// num表示出队人数
int num=0;
// res表示最后一个序号
int res=0;
while(1)
{
// 序号环形循环
i=(i+1)%n;
// 如果当前位置没有报数则这一轮报数
if(!people[i])
s++;
// 如果当前已经报到第m个则标记出队并且重置报数人数s
if(s==m)
{
people[i]=true;
s=0;
num++;
}
if(num==n)
{
res=i;
break;
}
}
return res;
}
idea:我们可以观察到,上述方法中使用了额外的存储空间people用于标记当前序号是否出列,并且在下一轮报数中,直接跳过已经报数的人,相当于我们并没有因为元素出列而改变相对序号,而仅仅是标记其已经报数,这样就可以维持原来的相对序号。其实,对于经典问题约瑟夫环,还有一种方法,即当某一元素出列后,我们可以将剩余未出列元素重新构造成一个环并且更改相对序号,再通过新环中的相对序号来推出旧环中的相对序号,那么这个过程我们可以使用递归来做。假设LastRemaining_Solution(n,m)表示n个人报数m最后留下的序号,则经过报数后,剩下n-1个人,仍然是报数m,那么LastRemaining_Solution(n-1,m)表示n-1个人报数m最后留下的序号,这两者有什么关系呢?假设现在10个人,报数3,即n=10,m=3,即初始为0、1、2、3、4、5、6、7、8、9,第一轮报数后2出列,故剩余0、1、3、4、5、6、7、8、9,由于存在空位,故我们需要重新构成环,即3、4、5、6、7、8、9、0、1,这样其相对序号即为0、1、2、3、4、5、6、7、8,由此我们可以推导出旧环与新环之间的序号关系,即约瑟夫环递推公式。
// LastRemaining_Solution(n,m)表示n个人报数m最后留下的序号
int LastRemaining_Solution(int n, int m)
{
// 如果最后只有一个人 则返回序号0
if(n==1)
return 0;
// n-1个人报数m最后留下的序号
int res=LastRemaining_Solution(n-1,m);
// 约瑟夫环递推公式
return (res+m)%n;
}



京公网安备 11010502036488号