剑指offer45题:圆圈中最后剩下的数

题目描述
n个人(编号0~(n-1))围成一个圈,从0开始报数,报到(m-1)的退出,剩下的人 继续从0开始报数。求最后剩下的那个人的编号。

解题思路:用一个数组来记录每个人的状态(是否退出圈子),然后开始计数,数到m就改变这个位置人的状态,同时把总人数减去1。从新开始计数,知道总人数只剩一个人,此时遍历数组,找到状态没有改变的那个人输出。

public class Solution {
    public int LastRemaining_Solution(int n, int m) {
        int count = 0;
        int people = n;
        int i = 0;
        int[] flag = new int[n];
        while(people > 1){
            if(flag[i] == 0){//如果状态为0,代表没有离开圈子,计数加1
                count ++;
            }
            if(count == m){//如果计数到m,就把这个位置状态置1,计数归零,总人数减1。
                flag[i] = 1;
                count = 0;
                people --;
            }
            i ++;
            if(i == n){//数到最后一个数时候从头开始数
                i = 0;
            }
        }
        //遍历数组找到状态没有改变的那个人
        for(int k = 0; k < flag.length; k ++){
            if(flag[k] == 0){
                return k;
            }
        }
        return -1;
    }
}

分析
这样的方法每次标记一个状态需要执行m次,一共要标记n-1次,所以时间复杂度是O(m*(n-1)),同时空间复杂度是O(n)。
接下来的内容都是复制一个大佬,原文链接如下:约瑟夫环的理解
约瑟夫环的介绍就不再叙述,自行百度一下。
这里拿a, b, c, d, e, f, g, h, i, j 来模拟10个人,设m=3.
下图的意思呢,就是模拟这10个人玩这个游戏到最后一个人存活下来的整个过程。绿色行表示在n = i 时,每个人的位置索引。因为在n = 10时,数一个m,杀死了c之后,后面的继续从0开始数,此时剩9个人,从0开始数,相当于这又是一个全新的游戏,只不过现在只剩9个人,而这9个人此时的位置索引也发生了变动。
图片说明

通过上图,我们可以很直观的看到,最终d存活了下来。那么,此时我们从下往上看图,看看每一轮d的位置索引是什么情况。当n = 1, 2, 3, 4, 5, 6, 7, 8, 9, 10时,d分别选择了位置索引为:0, 1, 1, 0, 3, 0, 3, 6, 0, 3。什么意思呢?可以这么理解,现在让你参与玩这个游戏,你不想死,怎么办呢,要是有1个人参与,ok,你不用死。要是总共2个人参与,你必须选择位置索引为1才能存活下来;要是总共有3个人参与,你必须选择位置索引为1才能存活;要是总共有4个人,你必须选择0…依次类推。你必须通过n来推算自己的索引位置才可以存活下来。
图片说明

那么我们此时用f(n)表示n个人的时候,能够存活的位置索引。由上图显然可以得到f(1)=0,f(2)=1,…,f(10)=3。但是我们要找的是通项公式。从图中也可以很直观的看到,n=9时d的位置索引就是n=10时循环左移了3位。那么可以得到f(10) = (f(9) + 3) % 10,因为是循环左移,所以需要加一个取余,防止越界。同理可以得到f(3) = (f(2)+3)%3,f(2) = (f(1) + 3) % 2。
从始至终,m只是一个常量。所以不影响结论。
所以约瑟夫环问题的公式:

f(n,m) = (f(n-1, m) + m) % n
代码如下:

public class Solution {
    public int LastRemaining_Solution(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;
    }
}

迭代时我们可以直接从i = 2 开始,因为 i = 1时存活的就是你自己,编号就是0,如果大于1就按照递推公式依次依次算出来i = n 时候的结果。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值