剑指offer打卡Day13:孩子们的游戏

题1:圆圈中最后剩下的数

题目描述

每年六一儿童节,牛客都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此。

HF作为牛客的资深元老,自然也准备了一些小游戏。

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

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

示例

输入

5,3

返回值

3

解析:

  • 从故事中抽取题目,可整理得:

    给定一个由[0…n-1]构成的数组,第一次从0开始数m个数,然后删除,以后每次都从删除的数下一个位置开始数m个数,然后删除,直到剩余一个数字,找出那个数字。

    如图:

    在图中我们可以看到,在首次删除掉了(m-1)位元素后,之后都是不断在数组中***循环***遍历第m个数字进行删除。

    • 对于循环的操作,此时可以用到 % (取余)运算

    • 对于%运算的理解:

      • 3%10 = 1;(3/10=3......1)

        举例说明:

        在数组[1,2,3],从第一位元素开始从左往右循环数10次,第10次必定会落在第一位。

      • 数据结构中的循环队列也是运用这一操作(等下题刷到了再仔细讲解)

    • 综上对于本次的操作,我们可以借用链表与%运算求出(见解答一)

    • 不过答案提交之后显示运行时间位30+ms,总感觉太慢了需要优化。

  • 查阅资料得知此题与约瑟夫问题 十分相似:

    • 约瑟夫问题(有时也称为约瑟夫斯置换,是一个计算机科学和数学中的问题。在计算机编程的算法中,类似问题又称为约瑟夫环。又称“丢手绢问题”.)

    • 据说著名犹太历史学家Josephus有过以下的故事:在罗马人占领乔塔帕特后,39 个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。然而Josephus 和他的朋友并不想遵从。首先从一个人开始,越过k-2个人(因为第一个人已经被越过),并杀掉第k个人。接着,再越过k-1个人,并杀掉第k个人。这个过程沿着圆圈一直进行,直到最终只剩下一个人留下,这个人就可以继续活着。问题是,给定了和,一开始要站在什么地方才能避免被处决。Josephus要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏。

    • 最后可根据约瑟夫问题得出一个公式

             |—— 0                 n=1
      f(n,m)=|
             |—— [f(n-1,m)+m]%n    n>1
      
    • 具体推导可见博客:

      首先分析可以得知,这是一道约瑟夫环的问题。具体约瑟夫环原理可以参考下面链接。
      约瑟夫环——公式法(递推公式)

      约瑟夫环——公式法(递推公式)讲解非常详细,必看

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

解答:

//借用链表:
public int LastRemaining_Solution_withLinkedList(int n, int m) {
    LinkedList<Integer> list = new LinkedList<Integer>();
    for (int i = 0; i < n; i ++) {
        list.add(i);
    }
    int bt = 0;
    while (list.size() > 1) {
        bt = (bt + m - 1) % list.size();
        //int num = list.get(bt);
        list.remove(bt);
        //System.out.println("移除:"+num+"---->"+list);
    }
    return list.size() == 1 ? list.get(0) : -1;
}
/*
打印结果:与推理一致
[0, 1, 2, 3, 4, 5]
移除:2---->[0, 1, 3, 4, 5]
移除:5---->[0, 1, 3, 4]
移除:3---->[0, 1, 4]
移除:1---->[0, 4]
移除:4---->[0]
*/
//运用约瑟夫环问题推导公式:
 public int LastRemaining_Solution(int n, int m) {
     if (n <= 0) return -1;
     int index = 0;
     for (int i=2; i<=n; ++i) {
         index = (index + m) % i;
     }
     return index;
 }

你可能感兴趣的:(剑指offer,数据结构,数据结构,算法,java,链表)