孩子们的游戏(圆圈中最后剩下的数)

孩子们的游戏(圆圈中最后剩下的数)

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

如果没有小朋友,请返回-1


先介绍一个常见的算法,java中直接使用一个list来模拟,并使用一个索引cur类指向删除的位置,当cur的值为list的size,就让cur到头位置。

import java.util.*;
public class Solution {
    public int LastRemaining_Solution(int n, int m) {
        if(n<1||m<1){
            return -1;
        }
        List<Integer> list = new ArrayList<>();
        //构建list
        for(int i = 0;i<n;i++){
            list.add(i);
        }
        int cur = -1;
        while(list.size()>1){
            for(int i = 0;i<m;i++){
                cur++;
                if(cur == list.size()){
                    cur = 0;
                }
            }
            list.remove(cur);
            cur--;//cur--的原因,因为新的list中cur指向了下一个元素,为了保证移动m个准确性,所以cur向前移动一位。
        }
        return list.get(0);
    }
}

这个算法也比较好理解,但是可以看出来复杂度是很高的


下面介绍一种数学方法,通过推导出递推式来解决

首先定义最初的 n 个数字(0,1,…,n-1)中最后剩下的数字是关于 n 和 m 的方程为 f(n,m)。在这 n 个数字中,第一个被删除的数字是(m-1)%n,为简单起见记为 k。那么删除 k 之后的剩下 n-1 的数字为 0,1,…,k-1,k+1,…,n-1,并且下一个开始计数的数字是 k+1。相当于在剩下的序列中,k+1 排到最前面,从而形成序列 k+1,…,n-1,0,…k-1。该序列最后剩下的数字也应该是关于 n 和 m 的函数。由于这个序列的规律和前面最初的序列不一样(最初的序列是从 0 开始的连续序列),因此该函数不同于前面函数,记为 f’(n-1,m)。最初序列最后剩下的数字 f(n,m) 一定是剩下序列的最后剩下数字 f’(n-1,m),所以 f(n,m)=f’(n-1,m)。接下来我们把剩下的的这 n-1 个数字的序列 k+1,…,n-1,0,…k-1 作一个映射,映射的结果是形成一个从 0 到 n-2 的序列:

k+1 -> 0
k+2 -> 1

n-1 -> n-k-2
0 -> n-k-1

k-1 -> n-2

​ 把映射定义为 p,则 p(x)= (x-k-1)%n,即如果映射前的数字是 x,则映射后的数字是 (x-k-1)%n。对应的逆映射是 p-1(x)=(x+k+1)%n。由于映射之后的序列和最初的序列有同样的形式,都是从 0 开始的连续序列,因此仍然可以用函数 f 来表示,记为 f(n-1,m)。根据我们的映射规则,映射之前的序列最后剩下的数字 f’(n-1,m)= p-1 [f(n-1,m)]=[f(n-1,m)+k+1]%n。把 k=m%n-1 代入得到 f(n,m)=f’(n-1,m)=[f(n-1,m)+m]%n。

​ 经过上面复杂的分析,我们终于找到一个递归的公式。要得到 n 个数字的序列的最后剩下的数字,只需要得到 n-1 个数字的序列的最后剩下的数字,并可以依此类推。当 n=1 时,也就是序列中开始只有一个数字 0,那么很显然最后剩下的数字就是 0。我们把这种关系表示为:

​ 0 n=1
f(n,m)={
​ [f(n-1,m)+m]%n n>1

​ 尽管得到这个公式的分析过程非常复杂,但它用递归或者循环都很容易实现。最重要的是,这是一种时间复杂度为 O(n),空间复杂度为 O(1) 的方法,因此无论在时间上还是空间上都优于前面的思路。

public class Solution {
   public int LastRemaining_Solution(int n,int m) {
       if(n==0) return -1;
       
      int s=0;
       /*
       每次循环就是n+1,在循环中i的值和n的值是一致的
       */
      for(int i=2;i<=n;i++){
          s=(s+m)%i;
      }
      return s;
   }
}
posted @ 2020-03-07 10:24  别再闹了  阅读(94)  评论(0)    收藏  举报