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

时间:2022-07-24
本文章向大家介绍Day46:孩子们的游戏(圆圈中最后剩下的数),主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

剑指Offer_编程题——孩子们的游戏(圆圈中最后剩下的数)

题目描述:

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

具体要求:

时间限制: C/C++ 1秒,其他语言2秒 空间限制: C/C++32M,其他语言64M

具体实现

背景介绍   这个问题是一个典型的约瑟夫问题,在介绍解题方法之前,首先给大家介绍约瑟夫问题。在维基百科中是这样介绍约瑟夫问题的。约瑟夫问题(有时也称为约瑟夫斯置换),是一个出现在计算机科学和数学中的问题。在计算机编程的算法中,类似问题又称为约瑟夫环。其问题是:

人们站在一个等待被处决的圈子里。 计数从圆圈中的指定点开始,并沿指定方向围绕圆圈进行。 在跳过指定数量的人之后,执行下一个人。 对剩下的人重复该过程,从下一个人开始,朝同一方向跳过相同数量的人,直到只剩下一个人,并被释放。

这就是典型的约瑟夫问题,根据这个问题,我们给出以下的解法: 思路一   上面介绍了约瑟夫的相关问题,接下来我们用约瑟夫思想来解决该题。具体的方法如下:首先定义n个人,报m次的解是f(n,m),n,m以及编号为0的小朋友确定了,最后的幸运儿也就确定了,第一次报数后,m-1的小朋友出列,接着从m开始报数,但是最后的解是一样的,令g(n-1,m)表示第二次抽人,解是等于f(n,m)的,这两种情况的排列方式不同,一个从0开始报,一个从m开始报,所以我们把第二种情况也排成从0开始报,就是每个人的下标都减少m即可,那么最后的幸运儿加上m,就得出在原来序列的下标了,解就是f(n-1,m)+m,构成了一个递归问题。但由于是一个循环的环,超出环从头排起,就对每一次环的长度进行取余。关系式为f(n,m) = (f(n-1,m) + m)%n。接下来我们分别用java和python将其实现: 1、我们首先用java将其实现:

import java.util.LinkedList;
public class Solution{
	public int LastRemaining_Solution(int n, int m){
		if(n <= 0 || m < 0)
			return -1;
		LinkedList<Integer> list = new LinkedList<>();
		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.get(0);
	}
}

代码效果图如图所示:

2、我们用python将其实现:

class Solution:
    def LastRemaining_Solution(self, n, m):
        if n < 1 or m < 1:
            return -1
        if n == 1:
            return 0
        
        Prevalue = 0
        for i in range(2, n+1):
            value = (Prevalue + m) % i
            Prevalue = value
        return value

代码效果图如图所示:

思路二   我们可以用拼接法来解决该问题。具体用java实现如下:

import java.util.LinkedList;
public class Solution{
	public int  LastRemaining_Solution(int n, int m) {
		if(n <= 0 || m < 0)
			return -1;
		LinkedList<Integer> list = new LinkedList<>();
		for(int i = 0; i < n; i++)
			list.add(i);
		int removeIndex = 0;
		while(list.size() > 1){
			for(int i = 0; i < m-1; i++){
				removeIndex++;
				if(removeIndex == list.size())
					removeIndex = 0;
			}
			list.remove(removeIndex);
			if(removeIndex == list.size())
				removeIndex = 0;
		}
		return list.get(0);
	}
}

代码效果图如图所示:

思路三:   根据本题题意可知:第一个要删除的是m-1,第二个要删除是2m-1,第三个要删除是3m-1,由于有n的限制即用求模来解决。具体我们用python将其实现:

class Solution:
	def LastRemaining_Solution(self, n, m):
		if not m or not n:
			return -1
		res = range(n)
		i = 0
		while len(res) > 1:
			i = (m + i - 1) % len(res)
			res.pop(i)
		return res[0]

代码效果图如图所示:

  这里我们需要西湖一的是:算法中i=(m+i-1)%len(res),其中为何要减一,是因为已经从res删除了一个数,res后面的数下标会在原来的基础上减少1,所以2m-1的下标变成了2m-2,从而整个算法就可以理解了。 算法四:   将0-n-1构建成一个环形链表,尾结点的next指向头结点。然后对链表进行循环操作,指针走m步删除当前结点,即将前一结点的next连接到后一节点,这样达到删除的效果,直到指针的next结点等于指针本身,这代表链表只剩下一个元素,退出循环返回该元素值即可:接下来我们用python将其实现:

class Node:
	def __init__(self, val):
		self.val = val
		self.next = None
class Solution:
	def LastRemaining_Solution(self, n, m):
		if n == 0:
			return -1
		if n == 1:
			return 0
		head = Node(0)
		cur = head
		for i in range(1, n):
			cur.next = Node(i)
			cur = cur.next
		cur.next = head
		p = cur
		while p.next != p:
			count = 0
			while count < m - 1:
				p = p.next
				count += 1
			p.next = p.next.next
		return p.val

代码效果图如图所示:

总结

  本道题通过与上一道题类似,均是通过实际问题来考察我们对链表以及约瑟夫问题的理解,我们对本题给出了四种解题思路,均分别用java和python将其实现。首先给出的就是我们介绍的约瑟夫问题来解决的问题,其次是根据拼接的方式将其解决。最后就是通过循环链表来将其解决。因此,我们在做题的时候,应该多次尝试各种方法,扩展自己的思维,写出优质的代码。总之,我们要继续加油,争取早日找到工作,Good Luck!!!

参考文献

[1] 白马长枪儒雅将 [2] weixin_43160613 [3] 草木向阳 [4] Mr-Yu23