剑指offer--孩子们的游戏

题目描述

每一年六一儿童节,牛客都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此。HF做为牛客的资深元老,天然也准备了一些小游戏。其中,有个游戏是这样的:首先,让小朋友们围成一个大圈。而后,他随机指定一个数m,让编号为0的小朋友开始报数。每次喊到m-1的那个小朋友要出列唱首歌,而后能够在礼品箱中任意的挑选礼物,而且再也不回到圈中,从他的下一个小朋友开始,继续0...m-1报数....这样下去....直到剩下最后一个小朋友,能够不用表演,而且拿到牛客名贵的“名侦探柯南”典藏版(名额有限哦!!^_^)。请你试着想下,哪一个小朋友会获得这份礼品呢?(注:小朋友的编号是从0到n-1)

class Solution {

public:

int LastRemaining_Solution(int n, int m)

{

vector<int> numbers;

for (int i = 0; i < n; i++)

{

numbers.push_back(i);

}//构建了一个数组

int nn = n;

int i = -1;

int mm = 0;

while (nn>0)

{

i++;

if (i >= n)

{

i = 0;

}

if (numbers[i] == -1)

continue;

mm++;

if (mm == m)

{

numbers[i] = -1;

mm = 0;

nn--;

}



}

return i;

}

};

这段程序居然跑了90多ms,以前都是3,4,5都算高的了,此次居然跑了这么多。不过也算是能写出来点。

 

  这就是咱们以前说过的约瑟夫环问题,以前有写过不过确实全忘了。最初拿到这个题目的时候有了一个大体的思路,可是发现传进来的参数是两个int类型的,发现不能用循环来处理操做,可是仔细想了想,是能够用vector容器建立一个数组,这里我用的思就是我建立一个数组,数组中全部的数据都是0,咱们来数每输到m的时候就把这个位置的数据变成0。而后继续数,当这个位置变成0的时候就表明这这个位置的数据没有了,数数字的时候就不算他。

while (nn>0)

{

i++;

if (i >= n)

{

i = 0;

}

if (numbers[i] == -1)

continue;

mm++;

if (mm == m)

{

numbers[i] = -1;

mm = 0;

nn--;

}



}

这里要注意的是若是咱们的下标移动到了i就要继续让他变回0,这是一个循环不断的进行的,

if (numbers[i] == -1)

continue;

 

若是说我数着遇到了一个地方的数据是-1,其实这里用了conti其实就是让i++,就是跳过这个,不过若是写i++就不对了,由于你可能连续着好几个-1的数据,这样就比较复杂,因此这里就用了continue。过了这一坎,就说明遇到了数据是0的有效数据,这时候就按让咱们的mm算是计数器++。

mm++;

if (mm == m)

{

numbers[i] = -1;

mm = 0;

nn--;

}

若是咱们的mm计数器数到了m大小,就让这个位置的数据变成-1,就算他出局了,而后让计数器清零,让咱们小孩的数目也就是循环的次数减去1.

 这里还有方法,不过本身对vector了解的很少,咱们能够不是简单的把数组中的位置变成-1,咱们能够把这个位置的数据直接删除,vector是提供这个功能的,erase函数,不过这个函数的参数是须要传入迭代器的,本身对迭代器了解的并非不少,因此这里就没有使用。不过这种方法是换汤不换药,不管怎么写都是经过咱们的程序来模拟咱们整个的游戏过程。因此当n比较大的时候是很浪费时间的一种状况。还有一个问题忽然想到了,这里记着i要是从-1开始而不能说从0开始,由于这个0是第一个的。

 说完这些,还有一个更简单的程序

class Solution {

public:

int LastRemaining_Solution(int n, int m)

{

if (n == 0) return -1;

int s = 0;

for (int i = 2; i <= n; i++){

s = (s + m) % i;

}

return s;

}

};

约瑟夫环问题大多数仍是经过递归来实现的,

class Solution {

public:

int LastRemaining_Solution(unsigned int n, unsigned int m)

{

if (n == 0)

return -1;

if (n == 1)

return 0;

else

return (LastRemaining_Solution(n - 1, m) + m) % n;

}

};

这两个的时间复杂度就变成了咱们常常见到的3,4左右。

这里咱们假设n=10,m=4,也就是有十个小孩,第一次数到4的时候是3这个数字,而后他就被删除了,这时候咱们就有了一个新的环,之前的第五个数字也就是4的位置变成了咱们新环的第一个位置,变成了0,这样数下去就行,可是有个问题,咱们怎么才能让第三行新产生的环,和咱们第二行的旧环创建起来联系。咱们能够看出来,咱们新环的数字是:(旧环的数字-m)%n,因此咱们当获得新环的下标以后咱们就能计算出来旧环的下标,这样一层一层下去,咱们的到最后的那个下标以后就能一层层返回去计算出来咱们上一层的坐标,就能算到最初始的那个下标。

   m+1    ->    0
    m+2    ->    1
    …
    n-1    ->    n-k-2
    0   ->    n-k-1
    …
    m-1   ->   n-2

 

因此旧环的数字=(新环的数字+m)%n,这里要注意的是%n是取余他和我们除是不同的,因此由新环推旧环的时候一样是%n,而不是说*n。这里须要注意一下。