题目先导:

  有N个人围成一圈,按照1~n的顺序编好号。从第一个人开始报数,报到2的人退出圈子,下一个人从1开始重新报数,报到3的人退出圈子。如此下去,直到留下最后一个人,求留下来最后一个人的编号。

 

 

首先这题是我之前写过的一个题,当时第一反应是数组,确实数组使用标记可以完成任务。

代码如下:

 1 #include<stdio.h>
 2 int main()
 3 {
 4     int a[1000]={0};//数组a大小为人数上限 
 5     int n,m=3,i,f=0,t=0,s=0;
 6     scanf("%d",&n); 
 7     do
 8     {
 9         t++;//逐个枚举圈中的所有位置
10         if(t>n)
11             t=1;//数组模拟环状,最后一个与第一个相连
12         if(!a[t])
13             s++;//第t个位置上有人则报数
14         if(s==m)//当前报的数是m
15         {
16             s=0;//计数器清零
17             
18             a[t]=1;//此处人已退圈,设置为空
19             f++;//退圈人数+1
20         }
21     }while(f!=n);//直到所有人都退圈为止
22     printf("%d",t);//输出最后一人编号
23     return 0;
24 }

但是N可以非常大,这种方法的时间复杂度是O(nm),所以效率比较低。

当然,还有其他解决方法。其实这是一个典型的约瑟夫问题。

我们可以把问题重新分析:

  为了方便程序设计我们将报号从1,2,3改为0,1,2(原因后面解释),为了使问题更具有普遍性将报数上限从3修改为m;

  那么对于n个人有一下序列:

       0,1,2,3,4,······,m-1,m,m+1,······,n-1,n

  那么有人(m-1)退出后序列应该为:

       m,m+1,······,n-1,n,0,1,2,3,4,······,m-2

  对应新的编号为:

       0,1,2,3,4,······,m-1,m,m+1,······,n-1

  如果我们一直列举到最后一个人的话

  最后的序列应该是

       1

  对应的编号为

       0

  但是n可以趋近无穷大,所以一次次列举是不现实的,所以我们可以分析下上述列举过程。

  我们先从有n个人开始,对这n个人中的第一个人开始编号为0,第二个为1...以此类推直到编号到m-1时,将m-1所代表的人剔除

则下一轮中有n-1个人,并以m开始编号,令其为0,以此类推。

  我们可以发现每一次剔除一个人,总人数就减少一个,当总人数sum = n到sum = n-1这个过程中,并不影响最终的结果,所以我们可以把总人数sum = n-1状态看做一个新序列,那么这个新的n-1序列的解仍是sum = n时的解,那么序列n-2的解就是序列n-1的解,所以我们一层层递推就可以发现原序列n的解就是序列长度为1时的解,对应的编号是0。

我们从正向思考,设题目的解在X长度下的编号为f(X),当长度为n时,编号为m的人,在新的序列n-1中的编号应该为f(n-1) = [ f(n) + m ] % n (因为出现了取模运算所以会出现0的结果,所以之前需要在编号时0开始)(不理解的话可以往下翻翻看看递推图)

  那么不难得出 f(n) = [ f(n-1) + m ] % ( n - 1 ) ,由此我们得出了递推公式。便可以设计一下程序:

 1 #include <stdio.h> 
 2 int main() 
 3 { 
 4     int n, s = 0,m = 3; 
 5     scanf("%d", &n); 
 6     for (int i = 2; i <= n; ++i) 
 7         s = (s+m)%i; 
 8     printf("%d\n", s+1); //加1是因为从0开始编号
 9     return 0; 
10 }

当然还可以设计以下标准递推函数:

1 int function(int n,int m){
2     if(n==2)  return 1;
3     return (function(n-1,m)+m)%(n-1);
4 }

但是不建议这样做,因为这种方式使用到了栈,对空间的消耗比较大。

其实这题深入下去会涉及到链表,但是最近时间比较紧,有时间再写链表。

附图:

 

 如果你细心的话,或者手写过程的话你会发现N之后的数字就是N在每一轮的编号。