n个人围成圈报数游戏
n个人围成圈报数游戏
n个人围成圈报数游戏
Description
有n个人围成一圈,顺序排号。从第一人开始报数(从1到3报数),凡报到3的人退出圈子,问最后留下的是原来第几号的那位。
Input
一行,一个数n。
Output
输出最后留下来的人的序号。
Sample Input 1
40
Sample Output 1
28
一、前期:一头雾水
看到这个题目时,我先在草稿纸上画了画,感觉写起来可能有点难度。
但是,真正打开Clode Blocks时,我才真正面对了它。
看着“Hello World”,我想开始写,却发现无从下手:
怎么让报数3的人走开?
想不出来这个问题,我连一个“int”都打不出来。
这个问题难住了我。
二、中期:希望渺茫
循环报数,已经走开的人不参与接下来的报数。
我感觉这就是这个题的难点。
想出来,就能做出来;想不出,就做不出来。
我想了十分钟,仍旧是找不到方向。
我想过上网查询,但却又不甘心。
这时,我想到一个理论:
做不出来一道题时,可以先放一放,先做后面的题,做着做着前面的那个题也就能做出来了。
于是我先去做了它后面的那道题:CP1487用指针实现数据交换(难度:简单)。
呃……四五行的代码……
再后面,就不是指针的题了。
于是我又回到了这道题。
三、后期:峰回路转
其实我还是没有做出来。
并不存在说,做一道简单题后,就能做出中等题的说法。
于是我去吃了个饭。
吃完饭回来,灵光一闪……不对。
没有灵光。
我还是只能在草稿本上写写画画,希冀那一刹那的灵光。
它来了。
没有来由地,我突然想到:如果把报数3的数字赋值为0,会怎么样?
这就是正解。
点击查看代码
#include <stdio.h>
#include <stdlib.h>
int main()
{
int i,count=0,n,remain,number[100];
scanf("%d",&n);
remain=n;
for(i=0; i<n; i++)
{
number[i]=i+1;
}
while(remain>1)
{
for(i=0; i<n; i++)
{
if(number[i]!=0)
{
count++;
count=count%3;
}
if(count==0)
{
number[i]=0;
}
}
remain=0;
for(i=0; i<n; i++)
{
if(number[i]!=0)
remain++;
}
}
for(i=0; i<n; i++)
{
if(number[i]!=0)
printf("%d\n",number[i]);
}
return 0;
}
四、尾声
我终究是做了出来。
指针嘛,程序都出来了,插进去还不简单?
——————————
有点烂尾……
可能是我太菜了,但我确实花了很长时间才想出来0这个操作……
另外,前面的小故事,看看就好,不要笑我(* /ω\*)
下面是思考与总结。
五、回归问题本身
在做出来后,我上网查询了一下这个问题,它是C语言经典例题(还有名字“约瑟夫问题/约瑟夫环”),并且网上有很多讲解,也有多种方法。
回忆我做这道题时的想法,发现其实赋值0也可以改为赋值-1之类的,只要赋同一个值,非正数就行。
总之,通过这道题目,我领悟到了两个小妙招:
1.“离开/退出”:可以通过赋值0/负数;
2.“围成一圈”:可以通过设置一个变量,在循环到第一个值时继续++就行。
六、另解
点击查看代码
#include <stdio.h>
#include <stdlib.h>
int main()
{
int number[100],n,i,pointer=0,count=0,remain;
scanf("%d",&n);
remain=n;
for(i=0; i<n; i++)
{
number[i]=1;
}
do{
pointer++;
if(pointer>n)
pointer%=n;
if(number[pointer-1]==1)
{
count++;
if(count==3)
{
number[pointer-1]=0;
count=0;
remain--;
}
}
}while(remain!=1);
for(i=0; i<n; i++)
{
if(number[i]==1)
printf("%d",i+1);
}
return 0;
}
受到魔术师这题的启发,我想到可以使用类似的思维处理该题。
优点:在检测到存储数字是0后,直接跳过(不报数),而非报上一个人的数,因此可以直接在报数3时减少剩余人数,而不需要在所有人报完一轮后,统计剩余人数。
以下是AI对比两个代码的优略:
对于以上两个代码的比较,我们可以从以下几个方面进行评估:
1. 代码逻辑和可读性:
- 第一个代码使用了嵌套的for循环来实现报数和删除操作,代码逻辑相对简单,易于理解。
- 第二个代码使用了do-while循环,并使用了指针来表示当前报到的人,在代码逻辑上更加清晰。
2. 性能:
- 第一个代码在每次删除一个人后,需要重新计算剩余人数,可能导致性能损失。
- 第二个代码使用了remain变量来记录剩余人数,并使用了pointer指针来表示当前报到的人,可以减少循环次数,性能上可能更优。
3. 内存占用:
- 第一个代码使用了一个数组number来表示每个人的状态,占用了一定的内存空间。
- 第二个代码同样使用了一个数组number来表示每个人的状态,但同时还使用了一个pointer变量,相对而言可能占用更多的内存空间。
4. 输入验证和异常处理:
- 两个代码都缺乏输入验证和异常处理的代码,可能在输入错误数据时出错。
综上所述,第一个代码的优点在于逻辑简单、易于理解,代码量较少;第二个代码的优点在于逻辑清晰,可能在性能上略有优势。然而,两个代码都需要添加输入验证和异常处理的代码,以保证程序的稳定性和可靠性。根据具体需求和对代码可读性、性能和内存占用的要求,选择适合的代码实现。