7-2 猴子选大王

题目要求

7-2 猴子选大王 (10分) 一群猴子要选新猴王。新猴王的选择方法是:让N只候选猴子围成一圈,从某位置起顺序编号为1~N号。从第1号开始报数,每轮从1报到3,凡报到3的猴子即退出圈子,接着又从紧邻的下一只猴子开始同样的报数。如此不断循环,最后剩下的一只猴子就选为猴王。请问是原来第几号猴子当选猴王?

输入格式:
输入在一行中给一个正整数N(≤1000)。
输出格式:
在一行中输出当选猴王的编号。
输入样例:

11

输出样例:

7

让我们先来看看输入样例中11个猴子的选举情况:

1 2 3 4 5 6 7 8 9 10 11
第一轮 1 2 3 1 2 3 1 2 3 1 2
第二轮 3 1 2 3 1 2 3 1
第三轮 2 3 1 2 3
第四轮 1 2 3
第五轮 1 2
第六轮 3 1
第七轮 2

所以最后7号猴子被选为大王。

链表法

很容易想到循环链表,每次循环只需将报数为3的猴子代表的节点从链表中删除(即题目中的退出圈子),使用双向或单向链表皆可,这里我使用单向循环链表。

  • 关于头结点,在链表中,使用头结点在操作上更方便,对于此题还有一个作用,可以将当前还在圈子的猴子个数存于头节点中,并以此作为是否继续遍历循环链表的依据。

节点内容以及循环链表示意如下图所示:
在这里插入图片描述
节点包含猴子的编号number、每次报数的号码data以及指向下一节点的next指针。
由于是单向链表,所以在找到data=3的节点再删除时,无法找到其前驱节点,所以当data=2时,就删除下一节点。

#include<stdio.h>
#include<malloc.h>
 
typedef struct Node{
	int number,data;
	struct Node *next;
} LinkList;

LinkList *create(int n){
	int i,num = 0;
	//定义头节点,普通节点,尾部节点;
	LinkList *head, *node, *p;
	//分配头节点地址
	head = (LinkList*)malloc(sizeof(LinkList));
	//若是空链表则头尾节点一样
	p = head;         
	head -> data = n;
	head -> number=0;
	for (i = 0; i < n; i++) {
		node = (LinkList*)malloc(sizeof(LinkList));
		node -> number = i + 1;
		node -> data = 0;
		p -> next = node;
		p = node;
	}
	//结束创建,构造循环链表 
	p -> next = head -> next;
	return head;
}

int main(){
	int i,n,num = 0;
	scanf("%d",&n);
	
	LinkList *p,*head = create(n);
	p = head -> next;
	int count = head -> data;
	while(count != 1){
		p->data = (num++) % 3 + 1;
		printf("%d-%d\n",p->number,p->data);
		if(p -> data == 2){
			p -> next = p -> next -> next;
			num+=1;
			count--;
		} 
		p = p -> next; 
	}
	printf("%d",p -> number);
	free(p);
	free(head);	
	return 0;
} 

数组法

如果不采用链表法也可以使用一个2维数组来解决,首先需要重新分析选出猴子大王过程。

分析:

在第一轮中淘汰3号、6号、9号,均为3的倍数,可以拓展思路,在第二轮报数中,表面上淘汰报数为3的猴子,实际上淘汰累计报数为3的倍数的猴子,下图更加直观地展现了这一过程。

在这里插入图片描述
图中括号中的数字代表累计报数,可以发现11只猴子中选出1个大王,淘汰的为累计报数为3的倍数的前10只猴子,即:3,6,9,12,15,18,21,24,27,30。

构造数组

这里二维数组的列数即为猴子个数,很好确定。但是行数因猴子个数的不同会有变化,具体行数可能可以通过数学公式计算,或者直接构造行数一个足够大的数组。数学方式确定行数的方法暂时我还没找到,读者有这方面的想法可以在评论区讨论;足够大的数组会造成不必要的空间浪费。所以秉持“环保”理念,这里只需构造两行的二维数组,每行都存储报数的数据,至于猴子本身的编号可根据下标确定,循环将后来的数据覆盖前面的数据即可。

数组初始化
为了与之后的判断相区别,这里将下标为0的列初始化为猴子编号,下标为1的列全部初始化为 -1

下标 0 1 2 3 4 5 6 7 8 9 10
0 1 2 3 4 5 6 7 8 9 10 11
1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1
  • 交替插入:遍历插入第0行后,遍历插入第1行,然后再遍历插入第1行,以此类推,节约空间。
    这种操作类似于有刷电机的原理,每次插入的行与之前插入的相反。代码中以 reset=!reset 来实现。

对于 第一轮 循环:向 下标为 1 的行插入数字,并判断 上一行 相同列数字是否为3的倍数,若是,则插入0,否则插入的数字按序增长。

下标 0 1 2 3 4 5 6 7 8 9 10
0 1 2 3 4 5 6 7 8 9 10 11
1 12 13 0 14 15 0 16 17 0 18 19

对于 第二轮 循环:向 下标为 0 的行插入数字,并判断 上一行 相同列数字是否为3的倍数,若是,则插入0,否则插入的数字按序增长。

下标 0 1 2 3 4 5 6 7 8 9 10
0 0 20 0 21 0 0 22 23 0 0 24
1 12 13 0 14 15 0 16 17 0 18 19

之后的循环以此类推。

  • 循环的退出条件: 由题意可知,当最后圈内猴子个数剩余1个时,即选出了大王。
    所以每当有新的3的倍数的累计报数产生,就相应地减少猴子个数。注意这里是新的3的倍数的累计报数,对于之间上一行已经是0的列来说,猴子个数count保持不变。
    即:

if(当前插入数字为3x && 上一行该列数字不为0){ count - -; }
if(当前插入数字为3x && 上一行该列数字为0) { count保持不变; }

完整代码如下:

#include<stdio.h> 

int main(){
	int i,n;
	scanf("%d",&n);
	int king[2][n];
	//二维数组初始化
	for(i=0;i<n;i++){
		king[0][i]=i+1;
		king[1][i]=-1;
	}
	int count=n,last=n,reset=0,flag;
	i=0;
	while(count!=1){
		for(i=0;i<n;i++){
			if(king[reset][i]%3==0 && king[!reset][i]%3==0){
				king[!reset][i]=0;
			}else if(king[reset][i]%3==0 && king[!reset][i]%3!=0){
				king[!reset][i]=0;
				count--;
			}else {
				king[!reset][i]=++last;
				flag=i+1;
			}
		}
		//交替插入
		reset=!reset;
	}
	printf("%d",flag);
	return 0;
}

通过添加输出语句可以显示每一轮的选举以及完整的选举过程:

每一轮选举结果:

在这里插入图片描述
完整选举过程:

在这里插入图片描述

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐