剑指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;
    }
原文地址:https://www.cnblogs.com/JefferyChenXiao/p/12249468.html