• 剑指Offer对答如流系列


    面试题62:圆圈中最后剩下的数字

    题目描述

    0, 1, …, n-1这n个数字排成一个圆圈,从数字0开始每次从这个圆圈里删除第m个数字。求出这个圆圈里剩下的最后一个数字。

    例如,从数字0开始每次删除第3个数字,则删除的前四个数字是2 0 4 1 因此最后剩下的数字是3

    在这里插入图片描述

    问题分析

    思路一:
    既然涉及到数据的频繁删除,可以考虑使用链表来存放数据,每次对长度取余数可以实现循环操作。

    思路二:
    这种问题规律性非常强,其实已经有对这一规律背后的数学模型的探究,即约瑟夫环

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

    上面这个场景与我们遇到的这个面试题仿佛。

    那么我们看看如何解决约瑟夫环场景的问题:

    n个数字的圆圈,不断删除第m个数字,可以将最后剩下的数字记为f(n,m)。

    n个数字中第一个被删除的数字是(m-1)%n, 我们记作k,k=(m-1)%n。

    那么剩下的n-1个数字就变成了:0,1,……k-1,k+1,……,n-1,我们把下一轮第一个数字排在最前面,并且将这个长度为n-1的数组映射到0~n-2。

    原始数字:k+1,……, n-1, 0, 1,……k-1

    映射数字:0 ,……,n-k-2, n-k-1, n-k,……n-2

    把映射数字记为x,原始数字记为y,那么映射数字变回原始数字的公式为 y=(x+k+1)%n。

    在映射数字中,n-1个数字,不断删除第m个数字,由定义可以知道,最后剩下的数字为f(n-1,m)。我们把它变回原始数字,由上一个公式可以得到最后剩下的原始数字是(f(n-1,m)+k+1)%n,而这个数字就是也就是一开始我们标记为的f(n,m),所以可以推得递归公式如下:

    f(n,m) =(f(n-1,m)+k+1)%n

    将k=(m-1)%n代入,化简得到:

    f(n,m) =(f(n-1,m)+m)%n

    f(1,m) = 0

    代码中可以采用循环或者递归的方法实现该递归公式。时间复杂度为O(n),空间复杂度为O(1)。

    问题解答

    思路一:

        public int LastRemaining(int n, int m) {
            if(n<1 || m<1) {
                return -1;
            }
            LinkedList<Integer> list = new LinkedList<Integer>();
            for(int i=0;i<n;i++) {
                list.add(i);
            }
            int removeIndex=0;
            while(list.size()>1){
                removeIndex=(removeIndex+m-1)%list.size();
                list.remove(removeIndex);
            }
            return list.getFirst();
        }
    

    思路二:

       public int LastRemaining(int n, int m) {
            if(n<1 || m<1) {
                return -1; 
            }
            
            int last=0;
            for(int i=2;i<=n;i++){
                last=(last+m)% i;  
            }
            return last;
        }
    
  • 相关阅读:
    CentOS创建vsftp进行读写操作的简单方法
    [知乎]经典古诗词
    js仿手机端九宫格登录功能
    Android Studio 小技巧合集
    Android MVPR 架构模式
    剖析OkHttp缓存机制
    Java Observable 模式
    使用Rxjava缓存请求
    ThreadPoolExecutor使用和思考(上)-线程池大小设置与BlockingQueue的三种实现区别
    ThreadPoolExecutor使用介绍
  • 原文地址:https://www.cnblogs.com/JefferyChenXiao/p/12249468.html
Copyright © 2020-2023  润新知