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