剑指offer-46.孩子们的游戏(圆圈中最后剩下的数)-vivo笔试题B2.报数(Java)

约瑟夫环

题目描述

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

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

1.模拟过程法-list

【解题思路】
Java List集合深入学习

小朋友们想象成从0到n-1的序号,每次报数到m-1,出队
接着从0开始报数
如果这组小朋友最后一个人报完数,从第一个再次开始
结果为list中最后剩下的小朋友。

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<>();
        
        //将0~n-1加入到list中
        for(int i=0;i<n;i++){
            list.add(i);
        }
        
        //count初值为-1,模拟正在报数的小朋友是这组list中的第几个,从0开始
        //举例:7个人,m=3,此时,存储后list,0-1-2-3-4-5-6,每报数到2的时候移除
        //第一次循环,移除2,count=2
        //由于再次报数的时候,3号报0,为list中的下标为2的数,所以count--
        //当count的值和list总数相同时,count置0,从0号学生报数
        int count = -1; 
        //list.size(),list中剩下的人数多于一个
        while(list.size()>1){
            for(int i=0;i<m;i++){
                count++;
                //如果list中,最后一个人报数,下一次是第一个人报数
               if(count==list.size()){
                    count=0;
                }
            }
            list.remove(count);
            //由于count已经出列,当前列表总个数要-1
            count--;
        }
        return list.get(0);               
    }
}

时间复杂度:O(mn)
空间复杂度:O(n)

2.模拟过程2.数组法

【解题思路】
将上述过程模拟成一个数组,出队的序号值置为-1
此题可转化为,一共有n个人,从1开始报数,报数为m的倍数,出队
设置三个个计数变量
第一个,为当前数组中还剩下多少人,即数组下标值不为-1的有多少个。初值为n,每次出队后,数目-1
第二个,为当前的序号,也就是数组下标。初值为-1,进入循环后,变为0。
如果序号>=n,则从0开始
如果当前数组下标的值为-1,则跳过
第三个,为报数的数目,初值为0,进入循环后,变为1,如果是m的倍数,将当前数组下标的值置为-1,代表出队,数目减1

import java.util.*;
public class Solution {
    public int LastRemaining_Solution(int n, int m) {
        int[] array = new int[n];
        if(n<1||m<1)
            return -1;
        
        //数组赋初值,可省略
        for(int i = 0;i<n;i++){
            array[i] = n;
        }
        
        //count为剩下人的个数,初始为n,最终剩下一个人的时候,返回这个人的下标
        int count = n;
        
        int result = -1; //当时报数的序号,也就是数组下标
        int s = 0;//m的倍数出列
        while(count>0){
            result++;
           
            //一组报完数后,从头开始,模拟环
            if(result>=n)
                result=0;
            
            //如果为-1,则进入下一次循环
            if(array[result]==-1)
                continue;
            
            //如果为-1,不计数
             s++;
            if(s%m==0){
                array[result]=-1;
                //出队后,总人数-1
                count--;
            }                      
        }        
        return result;//最后一个为-1的序号
    }  
}

时间复杂度:O(n)
空间复杂度:O(n)

3.递归法

【解题思路】
上述方法时间复杂度为O(m+n),我们可以不模拟整个过程,直接考虑剩下的“幸运者”是哪一个。

设函数F(n,m) 为 n个人报数,最终留下的幸运者的序号
如7个人报数,m=3,留下的是3号,即 F(7,3) = 3

原小朋友序号为:0,1,2,……,n-2,n-1

长度为n的序列,第一次删除(m-1)%n,剩下n-1长度的序列
即,第一个出队的序号 k =(m-1)%n

剩下的序列y可写为:0,1,2,……,k-1,k+1,……,n-2,n-1
也就是k+1,k+2,……,n-2,n-1,0,1,2,……,k-1
与 0,1,2,……,n-2形成映射,变为n-1个人报数问题

假设n-1个人,留下的是x,即x = F(n-1,m)
那么,n个人中,最终留下的是谁呢?
留下的是 (m-1)%n往后数 x+1个,即F(n,m) = (m-1+x+1)%n = (m+x)%n

public class Solution {
    public int LastRemaining_Solution(int n, int m) {
        if(n<1||m<1)
            return -1;
        
        return count(n,m);
        
    }
	//递归求F(n,m)
    public int count(int n,int m){
        if(n==1)
            return 0;        
        else
            return (count(n-1,m)+ m)%n;
    }
}

时间复杂度:O(n)
空间复杂度:O(n)

迭代法(循环)

F(1) = 0
F(2) = (F(1) + m)%2
F(3) = (F(2) + m)%3
……

F(n) = (m+ F(n-1) )%n

public class Solution {
    public int LastRemaining_Solution(int n, int m) {
        if(n <= 0)
            return -1;
        
        int result = 0;
        
        for(int i = 2;i<=n;i++){
            result = (result + m)%i;
        }
        
        return result;       
    }   
}

时间复杂度:O(n)
空间复杂度:O(1)

类似题

题目描述

今年7月份vivo迎来了新入职的大学生,现在需要为每个新同事分配一个工号。人力资源部同事小v设计了一个方法为每个人进行排序并分配最终的工号,具体规则是:

将N(N<10000)个人排成一排,从第1个人开始报数;如果报数是M的倍数就出列,报到队尾后则回到队头继续报,直到所有人都出列;

最后按照出列顺序为每个人依次分配工号。请你使用自己擅长的编程语言帮助小v实现此方法。

输入描述:

输入2个正整数,空格分隔,第一个代表人数N,第二个代表M:

输出描述:

输出一个int数组,每个数据表示原来在队列中的位置用空格隔开,表示出列顺序:

输入例子1:

6 3

输出例子1:

3 6 4 2 5 1

例子说明1:

6个人排成一排,原始位置编号即为1-6。
最终输出3 6 4 2 5 1表示的是原来编号为3的第一个出列,编号为1的最后一个出列。

仿模拟过程1代码-list

import java.io.*;
//新增
import java.util.*;

/**
 * Welcome to vivo !
 */

public class Main {
	//题目中给定代码
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String inputStr = br.readLine();
        int input[] = parseInts(inputStr.split(" "));
        String output = solution(input);
        System.out.println(output);
    }
	//题目中给定代码
    private static int[] parseInts(String[] strArr) {
        if (strArr == null || strArr.length == 0) {
            return new int[0];
        }
        int[] intArr = new int[strArr.length];
        for (int i = 0; i < intArr.length; i++) {
            intArr[i] = Integer.parseInt(strArr[i]);
        }
        return intArr;
    }
	//从这里开始写
    private static String solution(int[] input) {

        // TODO Write your code here
        int n = input[0]; //input数组第一个数为总人数 N
        int m = input[1]; //input数组第二个数为倍数 M
        
        List<Integer> list1 = new ArrayList<>();//利用list方法
        StringBuffer s = new StringBuffer();//由于结果数组中,每个数用空格隔开,所以用Strng格式
       
        for(int i=1;i<=n;i++){
            list1.add(i);
        }
        
        int count = -1;
        while(list1.size()>0){
            for(int j=0;j<m;j++){
                count++;
                if(count==list1.size()){
                    count = 0;
                }
            }
            //在字符串中加入list1被移除的序号,也就是报M倍数的员工的序号
            s.append(list1.get(count));
            //最后一个人后面不需要空格
            if(list1.size()!=1)
                s.append(" ");
            list1.remove(count);
            count--;            
        }
        
        return s.toString();//注意!!!
    }

}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值