约瑟夫环
题目描述
每年六一儿童节,牛客都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此。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();//注意!!!
}
}