首页 > 试题广场 >

孩子们的游戏(圆圈中最后剩下的数)

[编程题]孩子们的游戏(圆圈中最后剩下的数)
  • 热度指数:414092 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 256M,其他语言512M
  • 算法知识视频讲解
    每年六一儿童节,牛客都会准备一些小礼物和小游戏去看望孤儿院的孩子们。其中,有个游戏是这样的:首先,让 n 个小朋友们围成一个大圈,小朋友们的编号是0~n-1。然后,随机指定一个数 m ,让编号为0的小朋友开始报数。每次喊到 m-1 的那个小朋友要出列唱首歌,然后可以在礼品箱中任意的挑选礼物,并且不再回到圈中,从他的下一个小朋友开始,继续0... m-1报数....这样下去....直到剩下最后一个小朋友,可以不用表演,并且拿到牛客礼品,请你试着想下,哪个小朋友会得到这份礼品呢?

数据范围:
要求:空间复杂度 ,时间复杂度
示例1

输入

5,3

输出

3
示例2

输入

2,3

输出

1

说明

有2个小朋友编号为0,1,第一次报数报到3的是0号小朋友,0号小朋友出圈,1号小朋友得到礼物  
示例3

输入

10,17

输出

2
推荐

如果只求最后一个报数胜利者的话,我们可以用数学归纳法解决该问题,为了讨      论方便,先把问题稍微改变一下,并不影响原意:

 问题描述:n个人(编号0~(n-1)),从0开始报数,报到(m-1)的退出,剩下的人 继续从0开始报数。求胜利者的编号。

 我们知道第一个人(编号一定是m%n-1) 出列之后,剩下的n-1个人组成了一个新      的约瑟夫环(以编号为k=m%n的人开始):

        k  k+1  k+2  ... n-2, n-1, 0, 1, 2, ... k-2并且从k开始报0。

现在我们把他们的编号做一下转换:

k     --> 0

k+1   --> 1

k+2   --> 2

...

...

k-2   --> n-2

k-1   --> n-1

变换后就完完全全成为了(n-1)个人报数的子问题,假如我们知道这个子问题的解: 例如x是最终的胜利者,那么根据上面这个表把这个x变回去不刚好就是n个人情 况的解吗?!!变回去的公式很简单,相信大家都可以推出来:x'=(x+k)%n。

令f[i]表示i个人玩游戏报m退出最后胜利者的编号,最后的结果自然是f[n]。

递推公式

f[1]=0;

f[i]=(f[i-1]+m)%i;  (i>1)

有了这个公式,我们要做的就是从1-n顺序算出f[i]的数值,最后结果是f[n]。 因为实际生活中编号总是从1开始,我们输出f[n]+1。

class Solution {
public:
    int LastRemaining_Solution(unsigned int n, unsigned int m)
    {
        if(n==0)
            return -1;
        if(n==1)
            return 0;
        else
            return (LastRemaining_Solution(n-1,m)+m)%n;
    }
};

编辑于 2015-08-18 23:33:31 回复(68)
int LastRemaining_Solution(int n, int m) {
		if (m == 0 || n == 0) {
			return -1;
		}
		ArrayList<Integer> data = new ArrayList<Integer>();
		for (int i = 0; i < n; i++) {
			data.add(i);
		}
		int index = -1;
		while (data.size() > 1) {
//			System.out.println(data);
			index = (index + m) % data.size();
//			System.out.println(data.get(index));
			data.remove(index);
			index--;
		}
		return data.get(0);
	}

发表于 2015-10-12 16:22:18 回复(23)
import java.util.ArrayList;

public class Solution {
    public  int LastRemaining_Solution(int n, int m) {    
    	if(n==0)
    		return -1;
    	ArrayList<Integer> array=new ArrayList<>();
    	for(int i=0;i<n;i++){
    		array.add(i);
    	}
    	int tempIndex=(m-1)%array.size();//用于记录最初需清除的数字索引
    	while(array.size()!=1){ 
    		//System.out.println(array.get(tempIndex));
    		array.remove(tempIndex);
    		tempIndex=(tempIndex+(m-1))%array.size();//记录当前索引
    	}
    	return array.get(0);
    }
}

发表于 2017-02-18 23:45:03 回复(8)
开车啦
public class Solution {
    class Child{
        int index;
        Child next;
        public Child(int index){
            this.index = index;
        }
    }
    public int LastRemaining_Solution(int n, int m) {
        if(m==0)
            return -1;
        if(n==0)
            return 0;
        Child laosiji = new Child(0);
        Child kaichele = laosiji;
        for(int i = 1;i<n;i++){
            Child daidaiwo = new Child(i);
            kaichele.next = daidaiwo;
            kaichele = daidaiwo;
        }
        kaichele.next = laosiji;
        Child qingshuaka = laosiji;
        while(qingshuaka.next!=qingshuaka){
            Child dixueshengka = null;
            for(int i = 0;i<m-1;i++){
                dixueshengka = qingshuaka;
                qingshuaka = dixueshengka.next;
            } 
            qingshuaka = qingshuaka.next;
            dixueshengka.next = qingshuaka;
        }
        return qingshuaka.index;
    }
}

编辑于 2016-04-17 15:11:57 回复(9)
/*
//法一:C++实现 list容器+其迭代器实现圆形链表 (约瑟夫环问题)
class Solution {
public:
    int LastRemaining_Solution(int n, int m)//n为人数
    {
        if(n<1||m<1)
            return -1;
        list<int> numbers;
        for(int i=0;i<n;i++)
            numbers.push_back(i);
        list<int>::iterator current=numbers.begin();
        while(numbers.size()>1)
        {
            for(int i=1;i<m;i++)//走m-1步到达第m个数处
            {
                ++current;
                if(current==numbers.end())
                    current=numbers.begin();
            }
            
        	list<int>::iterator next=++current;
            if(next==numbers.end())
                next=numbers.begin();
            --current;
            numbers.erase(current);
            current=next;
        }
        return *current;//对迭代器取值,等价于对指针取值
    }
};
*/
//法二:找出规律,通项为:f(n,m)={f(n-1,m)+m}%n。
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;
    }
};

发表于 2017-07-28 22:30:45 回复(0)
Ron头像 Ron
   /*约瑟夫问题,求递推公式,每轮的序列中最后出序列的数都是同一个*/
     public int LastRemaining_Solution(int n,int m) {
        if(n < 1 || m < 1)
        	return -1;
    	if(n == 1){
        	return 0;
        }
        return (LastRemaining_Solution(n-1, m)+m)%n;
    }

编辑于 2015-07-10 16:49:05 回复(0)
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;
    }
}
时间复杂度:O(n)
空间复杂度:O(1)
发表于 2018-08-19 11:24:53 回复(1)
循环队列:
class node
{
	int num;
	node next;
	node (int num)
	{
		this.num = num;
	}
	}
public class Solution {
    public int LastRemaining_Solution(int n, int m) {
    	if(n == 0) return -1;
       node head = new node(0);
       node pre = head;
       node t = null;
       for(int i = 1; i< n; i++)
       {
    	    t = new node(i);
    	   pre.next = t;
    	   pre = t;
       }
       t.next = head;
       node p =t;
       for(int i = 0; i<n-1; i++)
       {
    	   for(int j = 0; j<m-1; j++)
    	   {
    		   p = p.next;
    	   }
    	   p.next = p.next.next;
       }
       return p.num;
    }
}

发表于 2016-08-03 18:15:15 回复(0)
只想到了最直接的方法——循环链表(😂
public class Solution {
    
    // 构造循环链表结构
    public static class Node {
        private int value;
        private Node pre;
        private Node next;
        
        public Node(int value) {
            this.value = value;
            this.pre = null;
            this.next = null;
        }
    }
    
    public int LastRemaining_Solution(int n, int m) {
        if (m == 0 && n == 0) {
            return -1;
        }
        Node head = new Node(0);
        Node cur = head;
        Node last = null;
        for(int i = 1; i < n; i++) {
            last = new Node(i);
            last.pre = cur;
            cur.next = last;
            cur = last;
        }
        cur = head;
        last.next = head;
        head.pre = last;
        int count = n;
        while (count != 1) {
            for (int i = 0; i < m - 1; i++) {
                cur = cur.next;
            }
            cur.pre.next = cur.next;
            cur.next.pre = cur.pre;
            cur = cur.next;
            count--;
        }
        return cur.value;
    }
}

发表于 2018-08-11 14:38:48 回复(0)
为了模拟游戏,并且考虑到删除节点的效率问题,所以使用 LinkedList.
import java.util.*;

public class Solution {
    /**
    题目:
    随机指定一个数m,让编号为0的小朋友开始报数。
    每次喊到m-1的那个小朋友要出列唱首歌,然后可以在礼品箱中任意的挑选礼物,并且不再回到圈中,
    从他的下一个小朋友开始,继续0...m-1报数....这样下去....
    直到剩下最后一个小朋友,可以不用表演,并且拿到牛客名贵的“名侦探柯南”典藏版(名额有限哦!!^_^)。
    请你试着想下,哪个小朋友会得到这份礼品呢?(注:小朋友的编号是从0到n-1)
    如果没有小朋友,请返回-1 
    题解:
    为了模拟游戏,并且考虑到删除节点的效率问题,所以使用 LinkedList
    */
    public int LastRemaining_Solution(int n, int m) {
        LinkedList<Integer> list = new LinkedList<>();
        for (int i = 0; i < n; i++) {
            // 游戏者编号 0 ~ n-1
            list.add(i);
        }
        // 游戏的开始位置
        int index = 0;
        // 循环删除节点,直至留下最后一个为止
        while (list.size() > 1) {
            // 从0 ~m-1 数数, 去掉数到 m-1 的人
            index = (index + m - 1) % list.size();
            list.remove(index);
        }
        // 输出要记得处理 n == 0 的情况,--> list.size() == 0
        return (list.size() == 0 ? -1 : list.get(0));
    }
}
发表于 2020-10-05 01:44:12 回复(0)
不用去计算各种公式,直接用循环链表不断循环m次删去节点,直至链表中只剩一个节点为止。
class Solution {
public:
    struct node
    {
        int no;
        node *next;
    };
    int LastRemaining_Solution(int n, int m)
    {
        node *head=new node;
        head->no=n-1;
        head->next=NULL;
        node *tail=head;//保留最后一个位置的节点一边将链表头尾连接
        for(int i=n-2; i>=0; i--)
        {
            node *p=new node;
            p->no=i;
            p->next=head;
            head=p;
        }
        tail->next=head;
        node *pre=tail;//保存被选中的节点前一个以便删去节点后重新连接链表
        while(head->next!=head)//判断条件是链表中只剩一个节点才停下
        {
            node *tmp=head;
            for(int i=0; i<m-1; i++)
            {
                tmp=tmp->next;
                pre=pre->next;
            }
            pre->next=tmp->next;
            head=pre->next;
            delete tmp;
        }
        return pre->no;
    }
};

编辑于 2017-08-02 09:43:43 回复(0)
 class Solution {
public:
    int LastRemaining_Solution(unsigned int n, unsigned int m)
    {
        
        if(n <= 0 && m <= 0) return -1; //蛋疼的特殊条件
        int t = 0;
        for(int i = 2; i <= n; i++)
            t = (t + m) % i;
        return t;
    }
};
/**
约瑟夫问题
递推公式
让f[i]为i个人玩游戏报m退出最后的胜利者的编号,最后的结果自然是f[n]
服了
f[1] = 0;
f[i] = (f[i - 1] + m) mod i;

因为递推,所以可以不用保存状态
代码如下 

*/ 

发表于 2016-05-02 00:31:35 回复(1)
说好的从1开始,为何5,3会是3而不是4,答案根本就是从0开始数的
发表于 2015-04-23 19:07:19 回复(3)
class Solution {
/*
参考剑指offer,纯手打
定义一个关于m 和n的方程,f(n,m),表示n个数字0,1,2,….n-1;
中每次删除第m个数字最后剩下的数字。
第一个被删除的数字(m-1)%n.
例如0,1,2,3,4,5,删除第3个,即2,那么(3-1)%6=0….2,商0余2,所以2就是那个被删除的数。
在删除第m个数字(定义为k)之后的序列为
0,1,2,…k-1,k+1,…n-1;
在进入下一次循环时删除第m个的时候从第k+1个数开始,这个序列为k+1,,,n-1,0,1,…k-1;函数因此定为f*(n-1,m)
再将这个映射我从0开始的序列,如下:

K+1 → 0;
K+2 → 1;
…
n-1 →  n-1-(k+1)=n-k-2;
0   →  n-k-2+1=n-k-1;
1   →  n-k;
…

k-1 → n-k-1+(k-1)=n-2;
映射p(x)=p(x-k-1)%n;表示映射钱的数字是x,映射后的数字是x-k-1。逆映射为
P*(x)=(x+k+1)%n.
这里记住无论循环多少次删除第m个元素最后剩下的数字是一样的。
有f*(n-1,m)=P*( f(n-1,m))=( f(n-1,m)+k+1)%n.=(f(n-1,m)+m)%n.
因为k=(m-1)%n=(m-1)

*/
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;
        
    }
};

发表于 2017-04-07 11:49:08 回复(4)
    /*
    *这道题我用数组来模拟环,思路还是比较简单,但是各种下标要理清
    */
    public static int findLastNumber(int n,int m){
        if(n<1||m<1) return -1;
        int[] array = new int[n];
        int i = -1,step = 0, count = n;
        while(count>0){   //跳出循环时将最后一个元素也设置为了-1
            i++;          //指向上一个被删除对象的下一个元素。
            if(i>=n) i=0;  //模拟环。
            if(array[i] == -1) continue; //跳过被删除的对象。
            step++;                     //记录已走过的。
            if(step==m) {               //找到待删除的对象。
                array[i]=-1;
                step = 0;
                count--;
            }        
        }
        return i;//返回跳出循环时的i,即最后一个被设置为-1的元素
    }

编辑于 2015-06-13 15:04:36 回复(54)
# -*- coding:utf-8 -*-
class Solution:
    def LastRemaining_Solution(self, n, m):
        # write code here
        childrens = [i for i in range(n)]
        index = 0
        final = -1
        while childrens:
            #k为当前离开的小朋友的下标
            k = (index + (m - 1)) % n
            final = childrens.pop(k)
            #当循环结束时,final指向最后一个小朋友
            n -= 1
            index = k
            #当下标k的元素被删除时,后面的元素向前一步,所以index仍然从k下标开始从后面计算下一个要离开的小朋友
        return final

发表于 2020-06-17 21:12:17 回复(0)
分析:约瑟夫环问题,可以用数组模拟,但需要维护是否出列的状态。使用LinkedList模拟一个环cycle,出列时删除对应的位置。
1. 报数起点为start(初始为0),则出列的位置为out = (start + m - 1) % cycle.size(),删除out位置;
2. 更新起点start = out,重复1直到只剩下一个元素。
时间复杂性、空间复杂度均为O(n)。
import java.util.LinkedList;
public class Solution {
    public int LastRemaining_Solution(int n, int m) {
        if (n < 1 || m < 1) {
            return -1;
        }
        LinkedList<Integer> cycle = new LinkedList<>();
        for (int i = 0; i < n; i++) {
            cycle.add(i);
        }
        int start = 0;
        while (cycle.size() > 1) {
            int out = (start + m - 1) % cycle.size();
            cycle.remove(out);
            start = out;
        }
        return cycle.remove();
    }
}

编辑于 2018-01-22 18:03:30 回复(0)
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;         
    }
}; 

第一个被删除的数字:(m-1) % n ...
(1)在删除第m个数字(定义为k,即 k = (m-1) % n),之后的序列为:0,…,k-1,k+1,…,n-1 ...
(2)将序列从第k+1个数开始重新排序为:n-k-1,…,n-2,0,…,n-k-2 (与上一行对应)...
(2-1)映射关系为:y = (x-k-1) % n,逆映射为:x = (y+k+1) % n = (y+m) % n(其中 y 和 n 会在每次重排后会改变,见下面分析)...
将以上(1)+(2)视为一步操作,进行n-1步操作即得结果,但是最终结果数字进行过n-1次重排,需要映射回初始数字 ...
考虑最后一次操作,此时仅有两个数字,且得到最后数字会把其重标为0,所以最后有:y = 0, n = 2,再由(2-1)映射关系反推即可 ...

发表于 2018-01-18 21:33:59 回复(0)
java实现
循环队列 时间复杂度N(nm)
import java.util.LinkedList;
public class Solution {
    public int LastRemaining_Solution(int n, int m) {
        LinkedList<Integer> list=new LinkedList<Integer>();        
        if(m<1||n<1)
            return -1;
        for(int i=0;i<n;i++){
            list.add(i);
        }
        int size=list.size();
        int current=0;
       
        while(list.size()>1){
            list.remove((current+m-1)%size);
            current=(current+m-1)%size;
            size--;            
        }
        return list.remove();
    }
}
约瑟夫 递归公式n=1时,f(n,m)=0;n>1时,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;
        if(n==1)
            return 0;
        else{
            return (LastRemaining_Solution(n-1,m)+m)%n;
        }
    }
}

发表于 2016-04-10 17:06:35 回复(0)
	public static int LastRemaining_Solution(int n, int m) {
		if(n == 0 || m == 0) {
			return -1;
		}
		int index = 0;
		int size = 0;
		LinkedList<Integer> linkedList = new LinkedList<>();
		for(int i =0; i < n; i++) {
			linkedList.add(i);
		}
		size = linkedList.size();
		while(size > 1) {
			linkedList.remove((index+m-1)%size);
			index = (index+m-1)%size;
			size--;
		}
		return linkedList.get(0);
	}

发表于 2016-03-23 21:32:31 回复(0)
用递归的方法:

class Solution {
private:
    int josephus(unsigned int n, unsigned int m){
        return n==0?0:(josephus(n-1,m)+m)%n;
    }
public:
    int LastRemaining_Solution(unsigned int n, unsigned int m)
    {
        if(n == 0) return -1;
        return josephus(n,m);
    }
};

非递归的方法:

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



发表于 2015-09-07 16:26:23 回复(0)

问题信息

难度:
910条回答 118325浏览

热门推荐

通过挑战的用户

查看代码