剑指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。这里需要注意一下。

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值