孩子们的游戏(圆圈中最后剩下的数):最直观的想法是,约瑟夫环。首先创建一个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; }